JavaScript 異步進(jìn)化說

原文鏈接: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.jsonb.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)境
  • babelbabel-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)換器時代的到來。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 異步編程對JavaScript語言太重要。Javascript語言的執(zhí)行環(huán)境是“單線程”的,如果沒有異步編程,根本...
    呼呼哥閱讀 7,409評論 5 22
  • 本文的示例代碼參考這里的async 目錄 引言 callbackasync ?PromisePromise對象bl...
    諾之林閱讀 543評論 1 5
  • 弄懂js異步 講異步之前,我們必須掌握一個基礎(chǔ)知識-event-loop。 我們知道JavaScript的一大特點(diǎn)...
    DCbryant閱讀 2,888評論 0 5
  • 單線程與異步 Javascript是單線程運(yùn)行、支持異步機(jī)制的語言。進(jìn)入正題之前,我們有必要先理解這種運(yùn)行方式。 ...
    貝聊科技閱讀 657評論 0 0
  • 本文首發(fā)在個人博客:http://muyunyun.cn/posts/7b9fdc87/ 提到 Node.js, ...
    牧云云閱讀 1,761評論 0 3

友情鏈接更多精彩內(nèi)容