<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
我們先從一個老生常談的問題開始。
由於javascript
是一門單執行緒的語言,所以我們早期來處理非同步場景的時候,大部分是通過回撥函數來進行處理的。
var fn = function(callback){ setTimeout(function(){ callback() },1000) } fn(function(){console.log('hello, pino')})
例如上面這個例子,fn函數是一個非同步函數,裡面執行的setTimeout
將會在1s之後呼叫傳入的callback
函數,列印出hello,pino
這個結果。
但是當我們有多個非同步操作的時候,就需要有多個非同步函數進行巢狀,程式碼將會變得更加臃腫和難以維護。
setTimeout(function(){ console.log('執行了') setTimeout(function(){ console.log('再次執行了') //..... },2000) },1000)
同樣的,還有一個例子: 假設我們有fn1,fn2,fn3三個非同步函數:
let fn1 = function(){ setTimeout(function(){ console.log('pino') },1000) } let fn2 = function(){ setTimeout(function(){ console.log('愛吃') },3000) } let fn3 = function(){ setTimeout(function(){ console.log('瓜') },2000) }
我們想順序對三個函數的結果進行順序列印,那麼使用傳統的回撥函數來實現的話,我們可以這樣寫:
var makefn = function(text,callback,timer){ setTimeout(function(){ console.log(text) callback() },timer) } makefn('pino',function(){ makefn('愛吃',function(){ makefn('瓜',function(){ console.log('結束了~') },2000) },3000) },1000)
可以看到當回撥任務過多的時候,我們的程式碼將會變的非常臃腫,尤其是多個非同步函數之間層層巢狀,這就形成了回撥地獄。
使用回撥函數的方式來處理非同步任務,當回撥函數過多時,對開發者的心智負擔是非常重的。
promise
物件的出現其實是對js處理非同步任務邁出的一大步,它提供了非常多的針對非同步的處理方法,錯誤捕獲鏈式呼叫...
Promise
物件是一個建構函式,用來生成Promise
範例。
Promise
建構函式接受一個函數作為引數,該函數的兩個引數分別是resolve
和reject
。
resolve
函數: 將Promise
物件的狀態從“未完成”變為“成功”(即從 pending
變為 resolved
),在非同步操作成功時呼叫,並將非同步操作的結果,作為引數傳遞出去;
reject
函數: 將Promise
物件的狀態從“未完成”變為“失敗”(即從 pending
變為 rejected
),在非同步操作失敗時呼叫,並將非同步操作報出的錯誤,作為引數傳遞出去。
const person = new Promise((resolve,reject) => { let num = 6; if(num>5){ resolve() }else{ reject() } })
Promise
範例生成以後,可以用then
方法分別指定resolved
狀態和rejected
狀態的回撥函數。
promise.then(function(value) { // success }, function(error) { // failure });
then
方法可以接受兩個回撥函數作為引數。第一個回撥函數是Promise
物件的狀態變為resolved
時呼叫,第二個回撥函數是Promise
物件的狀態變為rejected
時呼叫。其中,第二個函數是可選的,這兩個函數都接受Promise
物件傳出的值作為引數。
例如我們將上面的順序列印三個非同步函數進行改造:
makefn('pino',function(){ makefn('愛吃',function(){ makefn('瓜',function(){ console.log('結束了~') },2000) },3000) },1000) //改造後 fn('pino',1000).then(function(){ return fn('愛吃',3000) }) .then(function(){ return fn('瓜',2000) }) .then(function(){ console.log('結束了~') })
可以看到改造完成後的程式碼變得非常具有可讀性和條理性。 由於本文的主角不是Promise物件,所以想要深入瞭解請移步:es6.ruanyifeng.com/#docs/promi…
ES2017 標準引入了 async
函數,使得非同步操作變得更加方便。
async
函數返回一個 Promise
物件,可以使用then
方法新增回撥函數。當函數執行的時候,一旦遇到await
就會先返回,等到非同步操作完成,再接著執行函數體內後面的語句。
// 函數前面加入async關鍵字 async function getAllData(name) { // 遇到await會暫停,並返回值 const data = await getData(name); const options = await getSelect(name); return options; } getAllData('pino').then(function (result) { console.log(result); });
下面繼續使用async
的方式來改造一下文章開頭的例子:
async function makeFn() { let fn1 = await fn1() let fn2 = await fn2() let fn3 = await fn3() }
async
函數的出現幾乎將非同步函數完全變為了同步的寫法,使非同步任務更容易維護。
形式上, Generator
函數是一個普通函數,但是有兩個特徵。
一是,function
關鍵字與函數名之間有一個星號;
二是,函數體內部使用yield
表示式,定義不同的內部狀態(yield
在英語裡的意思就是“產出”)。
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var p1 = helloWorldGenerator();
上面程式碼定義了一個 Generator
函數helloWorldGenerator
,它內部有兩個yield
表示式(hello和world
)
即該函數有三個狀態:hello,world
和 return
語句(結束執行)。
然後, Generator
函數的呼叫方法與普通函數一樣,也是在函數名後面加上一對圓括號。
不同的是,呼叫 Generator
函數後,該函數並不執行,返回的也不是函數執行結果,而是一個指向內部狀態的指標物件,也就是上一章介紹的遍歷器物件(Iterator Object
)。 下一步,必須呼叫遍歷器物件的next
方法,使得指標移向下一個狀態。
也就是說,每次呼叫next
方法,內部指標就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield
表示式(或return
語句)為止。
換言之, Generator
函數是分段執行的,yield
表示式是暫停執行的標記,而next
方法可以恢復執行。
p1.next() // { value: 'hello', done: false } p1.next() // { value: 'world', done: false } p1.next() // { value: 'ending', done: true } p1.next() // { value: undefined, done: true }
上面程式碼一共呼叫了四次next方法。
Generator
函數也可以進行非同步任務的處理,上面的async
函數就是Generator
函數的語法糖,而兩者之間最大的區別就是async
函數內建了自執行器,也就是說無需手動呼叫next()
方法,async
函數就會幫我們繼續向下執行,而Generator
函數不會自動呼叫next()
方法,只能進行手動呼叫,下面實現一個簡易執行器:
// 接受一個Generator函數 function run(gen){ var g = gen(); function next(data){ var result = g.next(data); if (result.done) return result.value; // 只要返回的dong不為true,沒有執行完畢,就繼續呼叫next函數,繼續執行 result.value.then(function(data){ next(data); }); } next(); } run(gen);
使用Generator
函數來該寫一下之前的案例,其實只需要將await
更換為yield
:
function* makeFn() { let fn1 = yield fn1() let fn2 = yield fn2() let fn3 = yield fn3() }
本文只是簡略的講解了Generator
函數和async
函數,如果像深入學習,請移步:
es6.ruanyifeng.com/#docs/async
es6.ruanyifeng.com/#docs/gener…
說了這麼多,今天的主角await-to-js
到底是幹啥的?解決了什麼問題❓
先來看一下作者的定義:
Async await wrapper for easy error handling without try-catch。
非同步等待封裝器,便於錯誤處理,不需要try-catch
。
先來看一下如何使用:
安裝
npm i await-to-js --save
對比一下使用await-to-js
後,我們在程式碼中處理錯誤捕獲有什麼不同,這裡使用async
函數進行處理:
// async的處理方式 function async getData() { try { const data1 = await fn1() } catch(error) { return new Error(error) } try { const data2 = await fn2() } catch(error) { return new Error(error) } try { const data3 = await fn3() } catch(error) { return new Error(error) } }
// 使用await-to-js後 import to from './to.js'; function async getData() { const [err, data1] = await to(promise) if(err) throw new (error); const [err, data2] = await to(promise) if(err) throw new (error); const [err, data3] = await to(promise) if(err) throw new (error); }
可以看到,使用await-to-js後我們的程式碼變得精簡了許多,在使用async
函數時,需要手動使用try...catch
來進行錯誤捕獲,而await-to-js
直接就可以將錯誤返回給使用者。
所以根據上面的例子,可以得出結論,await-to-js
的作用就是封裝了錯誤捕獲的處理常式,使非同步的操作更加的方便。
那麼await-to-js
是如何實現的呢?
其實await-to-js
的原始碼非常短,只有15行,可以直接看一下原始碼中是如何實現的(為了檢視原始碼更加的直觀,下面的原始碼已經去除了typescript
語法):
export function to( promise, errorExt ){ return promise .then((data) => [null, data]) .catch((err) => { if (errorExt) { const parsedError = Object.assign({}, err, errorExt); return [parsedError, undefined]; } return [err, undefined]; }); }
可以看到await-to-js
中直接返回了to
函數,他接受兩個引數,promise
和errorExt
,其中promise
引數接受一個Promis物件,而errorExt
引數是可選的,先來看一下如果不傳入errorExt
引數是什麼樣子的:
export function to(promise, errorExt){ // 使用then和catch來執行和捕獲錯誤 return promise .then((data) => [null, data]) .catch((err) => { return [err, undefined]; }); }
to
函數直接返回了傳入的Promise
物件,並定義了then
函數和catch
函數,無論成功還是失敗都返回一個陣列,陣列的第一項是錯誤結果,如果執行成功則返回null
,執行失敗則返回錯誤資訊,陣列的第二項為執行結果,執行成功則返回響應成功的結果,如果執行失敗則返回undefined
,這也是非常符合預期的。
那麼第二個引數是幹什麼的呢?第二個引數errorExt
是可選的,他接收一個物件,主要用於接收使用者自定義的錯誤資訊,然後使用Object.assign
將自定義資訊與錯誤資訊合併到一個物件,返回給使用者。
.catch((err) => { if (errorExt) { // 合併錯誤物件:預設錯誤資訊+使用者自定義錯誤資訊 const parsedError = Object.assign({}, err, errorExt); // 返回錯誤結果 return [parsedError, undefined]; } });
剛開始看原始碼的時候各種不適應,但是隻要沉下心去一步一步的偵錯,結合測試用例,有些東西真的沒有想象中那麼難,主要還是重在行動,想到了一個念頭和想法就趕緊去做,拒絕拖沓,只有真正的行動去學習,去獲取,去感知,才能真正的進步!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45