原文鏈接:http://www.luckyjing.com/posts/js/async.html
本期的關(guān)鍵字為:callback timeout Promise generator async
每一個(gè)切圖仔們都在異步的路上掙扎過(guò)溜嗜,前端世界變化頻繁只损,全世界的各種大神也在不斷地在豐富著異步輪子调衰,從草案到標(biāo)準(zhǔn)也經(jīng)歷了相當(dāng)長(zhǎng)的一段時(shí)間,這篇文章帶你清晰地遍歷一遍異步的寫法,從回調(diào)地獄到優(yōu)雅“同步”巨朦。
文章主線
- 本文剛開始介紹最常見(jiàn)的異步函數(shù)回調(diào)執(zhí)行的形式送矩,并介紹Async庫(kù)進(jìn)行異步寫法的改進(jìn)。
- 隨后介紹ES6規(guī)范里面的Promise蝌以,它是接下來(lái)的異步改進(jìn)形式的基礎(chǔ)炕舵。
- 接下來(lái)為ES6規(guī)范里的Genetator,它提供了一個(gè)可以暫停與恢復(fù)的內(nèi)部迭代器,本身并不具有改進(jìn)異步的特點(diǎn)跟畅,但是可以使用co庫(kù)結(jié)合Promise實(shí)現(xiàn)優(yōu)雅的異步寫法咽筋。
- 最后為ES7中的async和await關(guān)鍵字,它相當(dāng)于內(nèi)部實(shí)現(xiàn)了co庫(kù)的封裝徊件,所以使用起來(lái)也與co庫(kù)更加相似奸攻,只不過(guò)相對(duì)于Generator里面的yield關(guān)鍵字,更具有語(yǔ)義化虱痕。
開胃菜
異步線程總是在JavaScript主線程空閑后(也就是for循環(huán)執(zhí)行完畢)進(jìn)行執(zhí)行睹耐,所以我們會(huì)觀察到in loop會(huì)先于任何console.log(i)執(zhí)行,而且對(duì)于變量i創(chuàng)建了閉包部翘,所以最終的輸出為3個(gè)3
for (var i = 0; i < 3; i++) {
console.log('in loop');
setTimeout(function () {
console.log(i);
}, 0);
}
異步三大情景
情景一
情景二
情景三
第一階段:回調(diào)與async庫(kù)
材料準(zhǔn)備:
- 安裝
async
庫(kù):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ù)量增加時(shí)窖梁,便無(wú)法控制,下圖展示了僅僅二層的異步回調(diào):
這個(gè)時(shí)候表牢,我們可以使用async庫(kù)進(jìn)行上述三大情景的執(zhí)行窄绒。
parallel,并行且無(wú)關(guān)的任務(wù)
準(zhǔn)備一系列任務(wù)數(shù)組,并且將數(shù)據(jù)傳入cb參數(shù)崔兴,隨后在cb中取得這些數(shù)據(jù)組成的數(shù)組彰导。
如果在執(zhí)行中某個(gè)任務(wù)拋出了異常,將不會(huì)再啟動(dòng)還未開始的任務(wù)敲茄,但是已經(jīng)開始的任務(wù)不受影響
series,串行且無(wú)關(guān)的任務(wù)
series更像同一時(shí)間只可以執(zhí)行一個(gè)任務(wù)的parallel
位谋,所以語(yǔ)法上和parallel
相同。
如果在執(zhí)行中某個(gè)任務(wù)拋出了異常堰燎,將不會(huì)再啟動(dòng)后續(xù)所有任務(wù)掏父。
waterfall,串行且相關(guān)的任務(wù)
waterfall
瀑布式的任務(wù),會(huì)按次序一個(gè)個(gè)執(zhí)行秆剪,但是數(shù)據(jù)的流向并不是終點(diǎn)的callback
赊淑,而是傳遞給下一個(gè)爵政,所以更像是流水線作業(yè),把數(shù)據(jù)的鍋拋來(lái)拋去陶缺。
第二階段:Promise
材料準(zhǔn)備:
-
Node
環(huán)境 - 瀏覽器端使用
babel
Promise
采用的是你先去執(zhí)行钾挟,隨后通知我,我來(lái)處理怎么做的形式饱岸,可以通過(guò)then
方法串起來(lái)掺出,then
方法依然返回的是一個(gè)新的Promise
實(shí)例,它的狀態(tài)取決于then
方法體內(nèi)的返回值苫费,如果是一般類型汤锨,則直接轉(zhuǎn)到resolve
狀態(tài),如果是Promise
對(duì)象百框,那么會(huì)轉(zhuǎn)入對(duì)這個(gè)新的Promise
的處理中來(lái)闲礼。
parallel
series 與 waterfall
我們可以發(fā)現(xiàn),使用了Promise之后琅翻,寫法格式上的主動(dòng)權(quán)交由我們控制位仁,所以實(shí)現(xiàn)series只需要自己模擬情景即可。
let result=[];
p1.then(data=>{
result.push(data); //可以將這里的data傳入第二個(gè)Promise生成對(duì)象方椎,即符合了waterfall情景
return p2;
}).then(data=>{
result.push(data);
}).then(()=>{
console.log(result);
});
第三階段:Generator
材料準(zhǔn)備:
-
Node
環(huán)境 - 瀏覽器端使用
babel
具體的Generator
的語(yǔ)法可以參考阮一峰的《ECMAScript 6 入門》
聂抢,主要講述結(jié)合co
庫(kù)進(jìn)行異步流程控制。
使用co
庫(kù)可以寫出非常便捷的“同步”的異步代碼棠众。
也可以使用一個(gè)數(shù)組去做并行的異步:
co(function*(){
return yield [
readFile('data/a.json'),
readFile('data/b.json')
]
}).then(res=>{
log(res);
})
在這里再給大家說(shuō)一下co
庫(kù)的基本原理琳疏,首先,我們先知道一下當(dāng)yield
一個(gè)Promise
對(duì)象會(huì)怎樣闸拿,自己在控制臺(tái)輸出后會(huì)發(fā)現(xiàn)會(huì)返回一個(gè)Promise
對(duì)象空盼,狀態(tài)為Pending
,而不是等Promise
運(yùn)行到resolve
或者reject
后再執(zhí)行到yield
新荤,那么co庫(kù)的基本原理便是如此揽趾,它會(huì)在每一個(gè)yield
暫停后,將返回的對(duì)象包裝成一個(gè)Promise
苛骨,隨即等Promise
狀態(tài)到達(dá)終點(diǎn)時(shí)篱瞎,再去激活原來(lái)函數(shù)的執(zhí)行,直到gen.next()
返回done為止痒芝,終止函數(shù)俐筋,并返回。
co原理
第四階段:async & await
材料準(zhǔn)備:
-
Node
環(huán)境 -
babel
及babel-preset-stage-3
當(dāng)嘗試了co庫(kù)之后严衬,再來(lái)看ES7
里面的關(guān)鍵字的話澄者,就非常好理解了,上圖:
可以發(fā)現(xiàn),寫法上幾乎和co庫(kù)一模一樣粱挡,只不過(guò)使用了更加語(yǔ)義化的關(guān)鍵字赠幕,也不用引入外來(lái)庫(kù),但是使用的基礎(chǔ)依然是Promise
對(duì)象抱怔,所以對(duì)于Promise
劣坊,一定要好好地理解。
總結(jié)
JavaScript
在異步流程控制的過(guò)程中屈留,經(jīng)驗(yàn)豐富的先驅(qū)者們創(chuàng)造了許多輪子,可供選擇的也有很多测蘑,但是基本思路都和Promise
相關(guān)灌危,所以玩轉(zhuǎn)異步的基礎(chǔ)便是掌握好Promise
,靜靜地等待編寫ES6/7
不再需要轉(zhuǎn)換器時(shí)代的到來(lái)碳胳。