原文鏈接:http://www.luckyjing.com/posts/js/async.html
本期的關(guān)鍵字為:callback timeout Promise generator async
每一個切圖仔們都在異步的路上掙扎過,前端世界變化頻繁,全世界的各種大神也在不斷地在豐富著異步輪子,從草案到標(biāo)準(zhǔn)也經(jīng)歷了相當(dāng)長的一段時間,這篇文章帶你清晰地遍歷一遍異步的寫法,從回調(diào)地獄到優(yōu)雅“同步”。
文章主線
- 本文剛開始介紹最常見的異步函數(shù)回調(diào)執(zhí)行的形式,并介紹Async庫進(jìn)行異步寫法的改進(jìn)。
- 隨后介紹ES6規(guī)范里面的Promise,它是接下來的異步改進(jìn)形式的基礎(chǔ)。
- 接下來為ES6規(guī)范里的Genetator,它提供了一個可以暫停與恢復(fù)的內(nèi)部迭代器,本身并不具有改進(jìn)異步的特點(diǎn),但是可以使用co庫結(jié)合Promise實(shí)現(xiàn)優(yōu)雅的異步寫法。
- 最后為ES7中的async和await關(guān)鍵字,它相當(dāng)于內(nèi)部實(shí)現(xiàn)了co庫的封裝,所以使用起來也與co庫更加相似,只不過相對于Generator里面的yield關(guān)鍵字,更具有語義化。

開胃菜
異步線程總是在JavaScript主線程空閑后(也就是for循環(huán)執(zhí)行完畢)進(jìn)行執(zhí)行,所以我們會觀察到in loop會先于任何console.log(i)執(zhí)行,而且對于變量i創(chuàng)建了閉包,所以最終的輸出為3個3
for (var i = 0; i < 3; i++) {
console.log('in loop');
setTimeout(function () {
console.log(i);
}, 0);
}
異步三大情景
情景一

情景二

情景三

第一階段:回調(diào)與async庫
材料準(zhǔn)備:
- 安裝
async庫:npm install async --save - 異步函數(shù),使用
fs.readFile進(jìn)行演示 - 準(zhǔn)備要讀取的相關(guān)文件,使用
a.json和b.json進(jìn)行演示
如果使用傳統(tǒng)的回調(diào)方式,在異步任務(wù)數(shù)量增加時,便無法控制,下圖展示了僅僅二層的異步回調(diào):

這個時候,我們可以使用async庫進(jìn)行上述三大情景的執(zhí)行。
parallel,并行且無關(guān)的任務(wù)
準(zhǔn)備一系列任務(wù)數(shù)組,并且將數(shù)據(jù)傳入cb參數(shù),隨后在cb中取得這些數(shù)據(jù)組成的數(shù)組。

如果在執(zhí)行中某個任務(wù)拋出了異常,將不會再啟動還未開始的任務(wù),但是已經(jīng)開始的任務(wù)不受影響
series,串行且無關(guān)的任務(wù)
series更像同一時間只可以執(zhí)行一個任務(wù)的parallel,所以語法上和parallel相同。

如果在執(zhí)行中某個任務(wù)拋出了異常,將不會再啟動后續(xù)所有任務(wù)。
waterfall,串行且相關(guān)的任務(wù)
waterfall瀑布式的任務(wù),會按次序一個個執(zhí)行,但是數(shù)據(jù)的流向并不是終點(diǎn)的callback,而是傳遞給下一個,所以更像是流水線作業(yè),把數(shù)據(jù)的鍋拋來拋去。

第二階段:Promise
材料準(zhǔn)備:
-
Node環(huán)境 - 瀏覽器端使用
babel
Promise采用的是你先去執(zhí)行,隨后通知我,我來處理怎么做的形式,可以通過then方法串起來,then方法依然返回的是一個新的Promise實(shí)例,它的狀態(tài)取決于then方法體內(nèi)的返回值,如果是一般類型,則直接轉(zhuǎn)到resolve狀態(tài),如果是Promise對象,那么會轉(zhuǎn)入對這個新的Promise的處理中來。

parallel

series 與 waterfall
我們可以發(fā)現(xiàn),使用了Promise之后,寫法格式上的主動權(quán)交由我們控制,所以實(shí)現(xiàn)series只需要自己模擬情景即可。
let result=[];
p1.then(data=>{
result.push(data); //可以將這里的data傳入第二個Promise生成對象,即符合了waterfall情景
return p2;
}).then(data=>{
result.push(data);
}).then(()=>{
console.log(result);
});
第三階段:Generator
材料準(zhǔn)備:
-
Node環(huán)境 - 瀏覽器端使用
babel
具體的Generator的語法可以參考阮一峰的《ECMAScript 6 入門》,主要講述結(jié)合co庫進(jìn)行異步流程控制。
使用co庫可以寫出非常便捷的“同步”的異步代碼。

也可以使用一個數(shù)組去做并行的異步:
co(function*(){
return yield [
readFile('data/a.json'),
readFile('data/b.json')
]
}).then(res=>{
log(res);
})
在這里再給大家說一下co庫的基本原理,首先,我們先知道一下當(dāng)yield一個Promise對象會怎樣,自己在控制臺輸出后會發(fā)現(xiàn)會返回一個Promise對象,狀態(tài)為Pending,而不是等Promise運(yùn)行到resolve或者reject后再執(zhí)行到yield,那么co庫的基本原理便是如此,它會在每一個yield暫停后,將返回的對象包裝成一個Promise,隨即等Promise狀態(tài)到達(dá)終點(diǎn)時,再去激活原來函數(shù)的執(zhí)行,直到gen.next()返回done為止,終止函數(shù),并返回。

co原理

第四階段:async & await
材料準(zhǔn)備:
-
Node環(huán)境 -
babel及babel-preset-stage-3
當(dāng)嘗試了co庫之后,再來看ES7里面的關(guān)鍵字的話,就非常好理解了,上圖:

可以發(fā)現(xiàn),寫法上幾乎和co庫一模一樣,只不過使用了更加語義化的關(guān)鍵字,也不用引入外來庫,但是使用的基礎(chǔ)依然是Promise對象,所以對于Promise,一定要好好地理解。
總結(jié)
JavaScript在異步流程控制的過程中,經(jīng)驗豐富的先驅(qū)者們創(chuàng)造了許多輪子,可供選擇的也有很多,但是基本思路都和Promise相關(guān),所以玩轉(zhuǎn)異步的基礎(chǔ)便是掌握好Promise,靜靜地等待編寫ES6/7不再需要轉(zhuǎn)換器時代的到來。