異步編程一直是JavaScript 編程的重大事項(xiàng)。關(guān)于異步方案, ES6 先是出現(xiàn)了 基于狀態(tài)管理的 Promise,然后出現(xiàn)了 Generator 函數(shù) + co 函數(shù),緊接著又出現(xiàn)了 ES7 的 async + await 方案。
本文力求以最簡(jiǎn)明的方式來(lái)疏通 async + await。
異步編程的幾個(gè)場(chǎng)景
先從一個(gè)常見(jiàn)問(wèn)題開(kāi)始:一個(gè)for 循環(huán)中,如何異步的打印迭代順序?
我們很容易想到用閉包,或者 ES6 規(guī)定的 let 塊級(jí)作用域來(lái)回答這個(gè)問(wèn)題。
for (let val of [1, 2, 3, 4]) { setTimeout(() => console.log(val),100);}// => 預(yù)期結(jié)果依次為:1, 2, 3, 4這里描述的是一個(gè)均勻發(fā)生的的異步,它們被依次按既定的順序排在異步隊(duì)列中等待執(zhí)行。
如果異步不是均勻發(fā)生的,那么它們被注冊(cè)在異步隊(duì)列中的順序就是亂序的。
for (let val of [1, 2, 3, 4]) { setTimeout(() => console.log(val), 100 * Math.random());}// => 實(shí)際結(jié)果是隨機(jī)的,依次為:4, 2, 3, 1返回的結(jié)果是亂序不可控的,這本來(lái)就是最為真實(shí)的異步。但另一種情況是,在循環(huán)中,如果希望前一個(gè)異步執(zhí)行完畢、后一個(gè)異步再執(zhí)行,該怎么辦?
for (let val of ['a', 'b', 'c', 'd']) { // a 執(zhí)行完后,進(jìn)入下一個(gè)循環(huán) // 執(zhí)行 b,依此類推}這不就是多個(gè)異步 “串行” 嗎!
在回調(diào) callback 嵌套異步操作、再回調(diào)的方式,不就解決了這個(gè)問(wèn)題!或者,使用 Promise + then() 層層嵌套同樣也能解決問(wèn)題。但是,如果硬是要將這種嵌套的方式寫(xiě)在循環(huán)中,還恐怕還需費(fèi)一番周折。試問(wèn),有更好的辦法嗎?
異步同步化方案
試想,如果要去將一批數(shù)據(jù)發(fā)送到服務(wù)器,只有前一批發(fā)送成功(即服務(wù)器返回成功的響應(yīng)),才開(kāi)始下一批數(shù)據(jù)的發(fā)送,否則終止發(fā)送。這就是一個(gè)典型的 “for 循環(huán)中存在相互依賴的異步操作” 的例子。
明顯,這種 “串行” 的異步,實(shí)質(zhì)上可以當(dāng)成同步。它和亂序的異步比較起來(lái),花費(fèi)了更多的時(shí)間。按理說(shuō),我們希望程序異步執(zhí)行,就是為了 “跳過(guò)” 阻塞,較少時(shí)間花銷。但與之相反的是,如果需要一系列的異步 “串行”,我們應(yīng)該怎樣很好的進(jìn)行編程?
對(duì)于這個(gè) “串行” 異步,有了 ES6 就非常容易的解決了這個(gè)問(wèn)題。
async function task () { for (let val of [1, 2, 3, 4]) { // await 是要等待響應(yīng)的 let result = await send(val); if (!result) { break; } }}task();從字面上看,就是本次循環(huán),等有了結(jié)果,再進(jìn)行下一次循環(huán)。因此,循環(huán)每執(zhí)行一次就會(huì)被暫停(“卡住”)一次,直到循環(huán)結(jié)束。這種編碼實(shí)現(xiàn),很好的消除了層層嵌套的 “回調(diào)地獄” 問(wèn)題,降低了認(rèn)知難度。
新聞熱點(diǎn)
疑難解答
圖片精選