JavaScript是如何工作的:事件循環(huán)和異步編程的崛起 + 5種使用 async/await 更好地編碼方式扣甲!

摘要: 深度理解JS事件循環(huán)8胄住1疑啊!

Fundebug經(jīng)授權(quán)轉(zhuǎn)載决摧,版權(quán)歸原作者所有。

此篇是 JavaScript是如何工作的第四篇凑兰,其它三篇可以看這里:

通過第一篇文章回顧在單線程環(huán)境中編程的缺陷以及如何解決這些缺陷來構(gòu)建健壯的JavaScript UI姑食。按照慣例拘鞋,在本文的最后,分享5個(gè)如何使用async/ wait編寫更簡(jiǎn)潔代碼的技巧矢门。

為什么單線程是一個(gè)限制?

在發(fā)布的第一篇文章中灰蛙,思考了這樣一個(gè)問題:當(dāng)調(diào)用堆棧中有函數(shù)調(diào)用需要花費(fèi)大量時(shí)間來處理時(shí)會(huì)發(fā)生什么?

例如祟剔,假設(shè)在瀏覽器中運(yùn)行一個(gè)復(fù)雜的圖像轉(zhuǎn)換算法。

當(dāng)調(diào)用堆棧有函數(shù)要執(zhí)行時(shí)摩梧,瀏覽器不能做任何其他事情——它被阻塞了物延。這意味著瀏覽器不能渲染,不能運(yùn)行任何其他代碼仅父,只是卡住了叛薯。那么你的應(yīng)用 UI 界面就卡住了,用戶體驗(yàn)也就不那么好了笙纤。

在某些情況下耗溜,這可能不是主要的問題。還有一個(gè)更大的問題是一旦你的瀏覽器開始處理調(diào)用堆棧中的太多任務(wù)省容,它可能會(huì)在很長(zhǎng)一段時(shí)間內(nèi)停止響應(yīng)抖拴。這時(shí),很多瀏覽器會(huì)拋出一個(gè)錯(cuò)誤,提示是否終止頁(yè)面:

JavaScript程序的構(gòu)建塊

你可能在單個(gè).js文件中編寫 JavaScript 應(yīng)用程序阿宅,但可以肯定的是候衍,你的程序由幾個(gè)塊組成,其中只有一個(gè)正在執(zhí)行洒放,其余的將在稍后執(zhí)行蛉鹿。最常見的塊單元是函數(shù)。

大多數(shù)剛接觸JavaScript的開發(fā)人員似乎都有這樣的問題往湿,就是認(rèn)為所有函數(shù)都是同步完成妖异,沒有考慮的異步的情況。如下例子:

你可能知道標(biāo)準(zhǔn) Ajax 請(qǐng)求不是同步完成的煌茴,這說明在代碼執(zhí)行時(shí) Ajax(..) 函數(shù)還沒有返回任何值來分配給變量 response随闺。

一種等待異步函數(shù)返回的結(jié)果簡(jiǎn)單的方式就是 回調(diào)函數(shù):

注意:實(shí)際上可以設(shè)置同步Ajax請(qǐng)求,但永遠(yuǎn)不要那樣做蔓腐。如果設(shè)置同步Ajax請(qǐng)求矩乐,應(yīng)用程序的界面將被阻塞——用戶將無(wú)法單擊、輸入數(shù)據(jù)回论、導(dǎo)航或滾動(dòng)散罕。這將阻止任何用戶交互,這是一種可怕的做法傀蓉。

以下是同步 Ajax 地欧漱,但是請(qǐng)千萬(wàn)不要這樣做:

這里使用Ajax請(qǐng)求作為示例,你可以讓任何代碼塊異步執(zhí)行葬燎。

這可以通過 setTimeout(callback误甚,milliseconds) 函數(shù)來完成。setTimeout 函數(shù)的作用是設(shè)置一個(gè)回調(diào)函數(shù)milliseconds后執(zhí)行谱净,如下:

function first() {
    console.log('first');
}
function second() {
    console.log('second');
}
function third() {
    console.log('third');
}
first();
setTimeout(second, 1000); // Invoke `second` after 1000ms
third();

輸出:

first
third
second

解析事件循環(huán)

這里從一個(gè)有點(diǎn)奇怪的聲明開始——盡管允許異步 JavaScript 代碼(就像上例討論的setTimeout)窑邦,但在ES6之前,JavaScript本身實(shí)際上從來沒有任何內(nèi)置異步的概念壕探,JavaScript引擎在任何給定時(shí)刻只執(zhí)行一個(gè)塊冈钦。

那么,是誰(shuí)告訴JS引擎執(zhí)行程序的代碼塊呢?實(shí)際上李请,JS引擎并不是單獨(dú)運(yùn)行的——它是在一個(gè)宿主環(huán)境中運(yùn)行的瞧筛,對(duì)于大多數(shù)開發(fā)人員來說,宿主環(huán)境就是典型的web瀏覽器或Node.js导盅。實(shí)際上较幌,現(xiàn)在JavaScript被嵌入到各種各樣的設(shè)備中,從機(jī)器人到燈泡白翻,每個(gè)設(shè)備代表 JS 引擎的不同類型的托管環(huán)境绅络。

所有環(huán)境中的共同點(diǎn)是一個(gè)稱為事件循環(huán)的內(nèi)置機(jī)制,它處理程序的多個(gè)塊在一段時(shí)間內(nèi)通過調(diào)用調(diào)用JS引擎的執(zhí)行。

這意味著JS引擎只是任意JS代碼的按需執(zhí)行環(huán)境恩急,是宿主環(huán)境處理事件運(yùn)行及結(jié)果杉畜。

例如,當(dāng) JavaScript 程序發(fā)出 Ajax 請(qǐng)求從服務(wù)器獲取一些數(shù)據(jù)時(shí)衷恭,在函數(shù)(“回調(diào)”)中設(shè)置“response”代碼此叠,JS引擎告訴宿主環(huán)境:"我現(xiàn)在要推遲執(zhí)行,但當(dāng)完成那個(gè)網(wǎng)絡(luò)請(qǐng)求時(shí)随珠,會(huì)返回一些數(shù)據(jù)驼修,請(qǐng)回調(diào)這個(gè)函數(shù)并給數(shù)據(jù)傳給它"正罢。

然后瀏覽器將偵聽來自網(wǎng)絡(luò)的響應(yīng),當(dāng)監(jiān)聽到網(wǎng)絡(luò)請(qǐng)求返回內(nèi)容時(shí),瀏覽器通過將回調(diào)函數(shù)插入事件循環(huán)來調(diào)度要執(zhí)行的回調(diào)函數(shù)驶乾。以下是示意圖:

這些Web api是什么?從本質(zhì)上說暖夭,它們是無(wú)法訪問的線程积蜻,只能調(diào)用它們志电。它們是瀏覽器的并發(fā)部分。如果你是一個(gè)Nojs.js開發(fā)者拉讯,這些就是 c++ 的 Api涤浇。

這樣的迭代在事件循環(huán)中稱為(tick)標(biāo)記,每個(gè)事件只是一個(gè)函數(shù)回調(diào)魔慷。

讓我們“執(zhí)行”這段代碼只锭,看看會(huì)發(fā)生什么:

1. 初始化狀態(tài)都為空,瀏覽器控制臺(tái)是空的的院尔,調(diào)用堆棧也是空的

2. console.log('Hi')添加到調(diào)用堆棧中

3. 執(zhí)行console.log('Hi')

4. console.log('Hi')從調(diào)用堆棧中移除蜻展。

5. setTimeout(function cb1() { ... }) 添加到調(diào)用堆棧。

6. setTimeout(function cb1() { ... }) 執(zhí)行邀摆,瀏覽器創(chuàng)建一個(gè)計(jì)時(shí)器計(jì)時(shí)纵顾,這個(gè)作為Web api的一部分。

7. setTimeout(function cb1() { ... })本身執(zhí)行完成隧熙,并從調(diào)用堆棧中刪除。

8. console.log('Bye') 添加到調(diào)用堆棧

9. 執(zhí)行 console.log('Bye')

10. console.log('Bye') 從調(diào)用調(diào)用堆棧移除

11. 至少在5秒之后幻林,計(jì)時(shí)器完成并將cb1回調(diào)推到回調(diào)隊(duì)列贞盯。

12. 事件循環(huán)從回調(diào)隊(duì)列中獲取cb1并將其推入調(diào)用堆棧。

13. 執(zhí)行cb1并將console.log('cb1')添加到調(diào)用堆棧沪饺。

14. 執(zhí)行 console.log('cb1')

15. console.log('cb1') 從調(diào)用堆棧中移除

16. cb1 從調(diào)用堆棧中移除

快速回顧:

值得注意的是躏敢,ES6指定了事件循環(huán)應(yīng)該如何工作,這意味著在技術(shù)上它屬于JS引擎的職責(zé)范圍整葡,不再僅僅扮演宿主環(huán)境的角色件余。這種變化的一個(gè)主要原因是ES6中引入了Promises,因?yàn)?code>ES6需要對(duì)事件循環(huán)隊(duì)列上的調(diào)度操作進(jìn)行直接、細(xì)度的控制啼器。

setTimeout(…) 是怎么工作的

需要注意的是旬渠,setTimeout(…)不會(huì)自動(dòng)將回調(diào)放到事件循環(huán)隊(duì)列中。它設(shè)置了一個(gè)計(jì)時(shí)器端壳。當(dāng)計(jì)時(shí)器過期時(shí)告丢,環(huán)境將回調(diào)放到事件循環(huán)中,以便將來某個(gè)標(biāo)記(tick)將接收并執(zhí)行它损谦。請(qǐng)看下面的代碼:

setTimeout(myCallback, 1000);

這并不意味著myCallback將在1000毫秒后就立馬執(zhí)行岖免,而是在1000毫秒后,myCallback被添加到隊(duì)列中照捡。但是颅湘,如果隊(duì)列有其他事件在前面添加回調(diào)剛必須等待前后的執(zhí)行完后在執(zhí)行myCallback

有不少的文章和教程上開始使用異步JavaScript代碼栗精,建議用setTimeout(回調(diào),0)闯参,現(xiàn)在你知道事件循環(huán)和setTimeout是如何工作的:調(diào)用setTimeout 0毫秒作為第二個(gè)參數(shù)只是推遲回調(diào)將它放到回調(diào)隊(duì)列中,直到調(diào)用堆棧是空的术羔。

請(qǐng)看下面的代碼:

console.log('Hi');
setTimeout(function() {
    console.log('callback');
}, 0);
console.log('Bye');

雖然等待時(shí)間被設(shè)置為0 ms赢赊,但在瀏覽器控制臺(tái)的結(jié)果如下:

Hi
Bye
callback

ES6的任務(wù)隊(duì)列是什么?

ES6中引入了一個(gè)名為“任務(wù)隊(duì)列”的概念。它是事件循環(huán)隊(duì)列上的一個(gè)層级历。最為常見在Promises 處理的異步方式释移。

現(xiàn)在只討論這個(gè)概念,以便在討論帶有Promises的異步行為時(shí)寥殖,能夠了解 Promises 是如何調(diào)度和處理玩讳。

想像一下:任務(wù)隊(duì)列是一個(gè)附加到事件循環(huán)隊(duì)列中每個(gè)標(biāo)記末尾的隊(duì)列。某些異步操作可能發(fā)生在事件循環(huán)的一個(gè)標(biāo)記期間嚼贡,不會(huì)導(dǎo)致一個(gè)全新的事件被添加到事件循環(huán)隊(duì)列中熏纯,而是將一個(gè)項(xiàng)目(即任務(wù))添加到當(dāng)前標(biāo)記的任務(wù)隊(duì)列的末尾。

這意味著可以放心添加另一個(gè)功能以便稍后執(zhí)行粤策,它將在其他任何事情之前立即執(zhí)行樟澜。

任務(wù)還可能創(chuàng)建更多任務(wù)添加到同一隊(duì)列的末尾。理論上叮盘,任務(wù)“循環(huán)”(不斷添加其他任務(wù)的任等等)可以無(wú)限運(yùn)行秩贰,從而使程序無(wú)法獲得轉(zhuǎn)移到下一個(gè)事件循環(huán)標(biāo)記的必要資源。從概念上講柔吼,這類似于在代碼中表示長(zhǎng)時(shí)間運(yùn)行或無(wú)限循環(huán)(如while (true) ..)毒费。

任務(wù)有點(diǎn)像 setTimeout(callback, 0) “hack”,但其實(shí)現(xiàn)方式是引入一個(gè)定義更明確愈魏、更有保證的順序:稍后觅玻,但越快越好想际。

回調(diào)

正如你已經(jīng)知道的,回調(diào)是到目前為止JavaScript程序中表達(dá)和管理異步最常見的方法溪厘。實(shí)際上胡本,回調(diào)是JavaScript語(yǔ)言中最基本的異步模式。無(wú)數(shù)的JS程序桩匪,甚至是非常復(fù)雜的程序打瘪,除了一些基本都是在回調(diào)異步基礎(chǔ)上編寫的。

然而回調(diào)方式還是有一些缺點(diǎn)傻昙,許多開發(fā)人員都在試圖找到更好的異步模式闺骚。但是,如果不了解底層的內(nèi)容妆档,就不可能有效地使用任何抽象出來的異步模式僻爽。

在下一章中,我們將深入探討這些抽象贾惦,以說明為什么更復(fù)雜的異步模式(將在后續(xù)文章中討論)是必要的胸梆,甚至是值得推薦的。

嵌套回調(diào)

請(qǐng)看以下代碼:

我們有一個(gè)由三個(gè)函數(shù)組成的鏈嵌套在一起须板,每個(gè)函數(shù)表示異步系列中的一個(gè)步驟碰镜。

這種代碼通常被稱為“回調(diào)地獄”。但是“回調(diào)地獄”實(shí)際上與嵌套/縮進(jìn)幾乎沒有任何關(guān)系习瑰,這是一個(gè)更深層次的問題绪颖。

首先,我們等待“單擊”事件甜奄,然后等待計(jì)時(shí)器觸發(fā)柠横,然后等待Ajax響應(yīng)返回,此時(shí)可能會(huì)再次重復(fù)所有操作课兄。

乍一看牍氛,這段代碼似乎可以將其異步性自然地對(duì)應(yīng)到以下順序步驟:

listen('click', function (e) {
    // ..
});

然后:

setTimeout(function(){
    // ..
}, 500);

接著:

ajax('https://api.example.com/endpoint', function (text){
    // ..
});

最后:

if (text == "hello") {
    doSomething();
}
else if (text == "world") {
    doSomethingElse();
}

因此,這種連續(xù)的方式來表示異步代碼似乎更自然烟阐,不是嗎?一定有這樣的方法搬俊,對(duì)吧?

Promises

請(qǐng)看下面的代碼:

var x = 1;
var y = 2;
console.log(x + y);

這非常簡(jiǎn)單:它對(duì)xy的值進(jìn)行求和,并將其打印到控制臺(tái)蜒茄。但是唉擂,如果xy的值丟失了,仍然需要求值扩淀,要怎么辦?

例如楔敌,需要從服務(wù)器取回xy的值啤挎,然后才能在表達(dá)式中使用它們驻谆。假設(shè)我們有一個(gè)函數(shù)loadXloadY``卵凑,它們分別從服務(wù)器加載xy的值。然后胜臊,一旦xy都被加載勺卢,假設(shè)我們有一個(gè)函數(shù)sum,它對(duì)xy`的值進(jìn)行求和象对。

它可能看起來像這樣(很丑黑忱,不是嗎?)

這里有一些非常重要的事情——在這個(gè)代碼片段中,我們將x和y作為異步獲取的的值勒魔,并且執(zhí)行了一個(gè)函數(shù)sum(…)(從外部)甫煞,它不關(guān)心x或y,也不關(guān)心它們是否立即可用冠绢。

當(dāng)然抚吠,這種基于回調(diào)的粗略方法還有很多不足之處。 這只是一個(gè)我們不必判斷對(duì)于異步請(qǐng)求的值的處理方式一個(gè)小步驟而已弟胀。

Promise Value

用Promise來重寫上例:

在這個(gè)代碼片段中有兩層Promise楷力。

fetchXfetchY 先直接調(diào)用,返回一個(gè)promise孵户,傳給 sum萧朝。 sum 創(chuàng)建并返回一個(gè)Promise,通過調(diào)用 then 等待 Promise夏哭,完成后检柬,sum 已經(jīng)準(zhǔn)備好了(resolve),將會(huì)打印出來方庭。

第二層是 sum(…) 創(chuàng)建的 Promise ( 通過 Promise.all([ ... ]) )然后返回 Promise厕吉,通過調(diào)用then(…)來等待。當(dāng) sum(…) 操作完成時(shí)械念,sum 傳入的兩個(gè) Promise 都執(zhí)行完后头朱,可以打印出來了。這里隱藏了在sum(…)中等待xy未來值的邏輯龄减。

注意:在sum(...)內(nèi)项钮,Promise.all([...])調(diào)用創(chuàng)建一個(gè) promise(等待 promiseX 和 promiseY 解析)。 然后鏈?zhǔn)秸{(diào)用 .then(...)方法里再的創(chuàng)建了另一個(gè) Promise希停,然后把 返回的 x 和 和(values[0] + values[1]) 進(jìn)行求和 并返回 烁巫。

因此,我們?cè)趕um(...)末尾調(diào)用then(...)方法 ?—? 實(shí)際上是在返回的第二個(gè) Pwwromise 上運(yùn)行宠能,而不是由Promise.all([ ... ])創(chuàng)建 Promise亚隙。 此外,雖然沒有在第二個(gè) Promise 結(jié)束時(shí)再調(diào)用 then方法 违崇,其時(shí)這里也創(chuàng)建一個(gè) Promise阿弃。

Promise.then(…) 實(shí)際上可以使用兩個(gè)函數(shù)诊霹,第一個(gè)函數(shù)用于執(zhí)行成功的操作,第二個(gè)函數(shù)用于處理失敗的操作:

如果在獲取xy時(shí)出現(xiàn)錯(cuò)誤渣淳,或者在添加過程中出現(xiàn)某種失敗脾还,sum(…) 返回的 Promise將被拒絕,傳遞給 then(…) 的第二個(gè)回調(diào)錯(cuò)誤處理程序?qū)?Promise 接收失敗的信息入愧。

從外部看鄙漏,由于 Promise 封裝了依賴于時(shí)間的狀態(tài)(等待底層值的完成或拒絕,Promise 本身是與時(shí)間無(wú)關(guān)的)棺蛛,它可以按照可預(yù)測(cè)的方式組成怔蚌,不需要開發(fā)者關(guān)心時(shí)序或底層的結(jié)果。一旦 Promise 決議旁赊,此刻它就成為了外部不可變的值媚创。

可鏈接調(diào)用 Promise 真的很有用:

創(chuàng)建一個(gè)延遲2000ms內(nèi)完成的 Promise ,然后我們從第一個(gè)then(...)回調(diào)中返回彤恶,這會(huì)導(dǎo)致第二個(gè)then(...)等待 2000ms钞钙。

注意:因?yàn)镻romise 一旦被解析,它在外部是不可變的声离,所以現(xiàn)在可以安全地將該值傳遞給任何一方芒炼,因?yàn)樗荒鼙灰馔獾鼗驉阂獾匦薷模@一點(diǎn)在多方遵守承諾的決議時(shí)尤其正確术徊。一方不可能影響另一方遵守承諾決議的能力本刽,不變性聽起來像是一個(gè)學(xué)術(shù)話題,但它實(shí)際上是承諾設(shè)計(jì)最基本和最重要的方面之一赠涮,不應(yīng)該被隨意忽略子寓。

使用 Promise 還是不用?

關(guān)于 Promise 的一個(gè)重要細(xì)節(jié)是要確定某個(gè)值是否是一個(gè)實(shí)際的Promise 笋除。換句話說斜友,它是否具有像Promise 一樣行為?

我們知道 Promise 是由new Promise(…)語(yǔ)法構(gòu)造的垃它,你可能認(rèn)為`` p instanceof Promise`是一個(gè)足夠可以判斷的類型鲜屏,嗯,不完全是。

這主要是因?yàn)榭梢詮牧硪粋€(gè)瀏覽器窗口(例如iframe)接收 Promise 值国拇,而該窗口或框架具有自己的 Promise 值洛史,與當(dāng)前窗口或框架中的 Promise 值不同,所以該檢查將無(wú)法識(shí)別 Promise 實(shí)例酱吝。

此外也殖,庫(kù)或框架可以選擇性的封裝自己的 Promise,而不使用原生 ES6 的Promise 來實(shí)現(xiàn)务热。事實(shí)上忆嗜,很可能在老瀏覽器的庫(kù)中沒有 Promise浪漠。

吞掉錯(cuò)誤或異常

如果在 Promise 創(chuàng)建中,出現(xiàn)了一個(gè)javascript一場(chǎng)錯(cuò)誤(TypeError 或者 ReferenceError)霎褐,這個(gè)異常會(huì)被捕捉,并且使這個(gè) promise 被拒絕该镣。

但是冻璃,如果在調(diào)用 then(…) 方法中出現(xiàn)了 JS 異常錯(cuò)誤,那么會(huì)發(fā)生什么情況呢?即使它不會(huì)丟失损合,你可能會(huì)發(fā)現(xiàn)它們的處理方式有點(diǎn)令人吃驚省艳,直到你挖得更深一點(diǎn):

看起來foo.bar()中的異常確實(shí)被吞噬了,不過,它不是嫁审。然而跋炕,還有一些更深層次的問題,我們沒有注意到律适。 p.then(…) 調(diào)用本身返回另一個(gè) Promise辐烂,該 Promise 將被 TypeError 異常拒絕。

處理未捕獲異常

許多人會(huì)說捂贿,還有其他更好的方法纠修。

一個(gè)常見的建議是,Promise 應(yīng)該添加一個(gè) done(…)厂僧,這實(shí)際上是將 Promise 鏈標(biāo)記為 “done”扣草。done(…) 不會(huì)創(chuàng)建并返回 Promise ,因此傳遞給 done(..) 的回調(diào)顯然不會(huì)將問題報(bào)告給不存在的鏈接 Promise 颜屠。

Promise 對(duì)象的回調(diào)鏈辰妙,不管以 then 方法或 catch 方法結(jié)尾,要是最后一個(gè)方法拋出錯(cuò)誤甫窟,都有可能無(wú)法捕捉到(因?yàn)?Promise 內(nèi)部的錯(cuò)誤不會(huì)冒泡到全局)密浑。因此,我們可以提供一個(gè) done 方法粗井,總是處于回調(diào)鏈的尾端肴掷,保證拋出任何可能出現(xiàn)的錯(cuò)誤。

ES8中改進(jìn)了什么 ?Async/await (異步/等待)

JavaScript ES8引入了 async/await背传,這使得使用 Promise 的工作更容易呆瞻。這里將簡(jiǎn)要介紹async/await 提供的可能性以及如何利用它們編寫異步代碼。

使用 async 聲明異步函數(shù)径玖。這個(gè)函數(shù)返回一個(gè) AsyncFunction 對(duì)象痴脾。AsyncFunction 對(duì)象表示該函數(shù)中包含的代碼的異步函數(shù)。

調(diào)用使用 async 聲明函數(shù)時(shí)梳星,它返回一個(gè) Promise赞赖。當(dāng)這個(gè)函數(shù)返回一個(gè)值時(shí)滚朵,這個(gè)值只是一個(gè)普通值而已,這個(gè)函數(shù)內(nèi)部將自動(dòng)創(chuàng)建一個(gè)承諾前域,并使用函數(shù)返回的值進(jìn)行解析辕近。當(dāng)這個(gè)函數(shù)拋出異常時(shí),Promise 將被拋出的值拒絕匿垄。

使用 async 聲明函數(shù)時(shí)可以包含一個(gè) await 符號(hào)移宅,await 暫停這個(gè)函數(shù)的執(zhí)行并等待傳遞的 Promise 的解析完成,然后恢復(fù)這個(gè)函數(shù)的執(zhí)行并返回解析后的值椿疗。

async/wait 的目的是簡(jiǎn)化使用承諾的行為

讓看看下面的例子:

function getNumber1() {
    return Promise.resolve('374');
}
// 這個(gè)函數(shù)與getNumber1相同
async function getNumber2() {
    return 374;
}

類似地漏峰,拋出異常的函數(shù)等價(jià)于返回被拒絕的 Promise 的函數(shù):

function f1() {
    return Promise.reject('Some error');
}
async function f2() {
    throw 'Some error';
}

await 關(guān)鍵字只能在異步函數(shù)中使用,并允許同步等待 Promise届榄。如果在 async 函數(shù)之外使用 Promise浅乔,仍然需要使用 then 回調(diào):

還可以使用“異步函數(shù)表達(dá)式”定義異步函數(shù)。異步函數(shù)表達(dá)式與異步函數(shù)語(yǔ)句非常相似铝条,語(yǔ)法也幾乎相同靖苇。異步函數(shù)表達(dá)式和異步函數(shù)語(yǔ)句之間的主要區(qū)別是函數(shù)名,可以在異步函數(shù)表達(dá)式中省略函數(shù)名來創(chuàng)建匿名函數(shù)班缰。異步函數(shù)表達(dá)式可以用作生命(立即調(diào)用的函數(shù)表達(dá)式)顾复,一旦定義它就會(huì)運(yùn)行。

var loadData = async function() {
    // `rp` is a request-promise function.
    var promise1 = rp('https://api.example.com/endpoint1');
    var promise2 = rp('https://api.example.com/endpoint2');
   
    // Currently, both requests are fired, concurrently and
    // now we'll have to wait for them to finish
    var response1 = await promise1;
    var response2 = await promise2;
    return response1 + ' ' + response2;
}

更重要的是鲁捏,在所有主流的瀏覽器都支持 async/await:

最后芯砸,重要的是不要盲目選擇編寫異步代碼的“最新”方法。理解異步 JavaScript 的內(nèi)部結(jié)構(gòu)非常重要给梅,了解為什么異步JavaScript如此關(guān)鍵假丧,并深入理解所選擇的方法的內(nèi)部結(jié)構(gòu)。與編程中的其他方法一樣动羽,每種方法都有優(yōu)點(diǎn)和缺點(diǎn)包帚。

編寫高度可維護(hù)性、非易碎異步代碼的5個(gè)技巧

1. 簡(jiǎn)化代碼:

使用 async/await 可以編寫更少的代碼运吓。 每次使用 async/await時(shí)渴邦,都會(huì)跳過一些不必要的步驟:使用.then,創(chuàng)建一個(gè)匿名函數(shù)來處理響應(yīng)拘哨,例如:**

// rp是一個(gè)請(qǐng)求 Promise 函數(shù)谋梭。
rp(‘https://api.example.com/endpoint1').then(function(data) {
    // …
});

和:

// `rp` is a request-promise function.
var response = await rp(‘https://api.example.com/endpoint1');

2. 錯(cuò)誤處理

Async/wait 可以使用相同的代碼結(jié)構(gòu)(眾所周知的try/catch語(yǔ)句)處理同步和異步錯(cuò)誤【肭啵看看它是如何與 Promise 結(jié)合的:**

function loadData() {
    try { // Catches synchronous errors.
        getJSON().then(function(response) {
            var parsed = JSON.parse(response);
            console.log(parsed);
        }).catch(function(e) { // Catches asynchronous errors
            console.log(e); 
        });
    } catch(e) {
        console.log(e);
    }
}

async function loadData() {
    try {
        var data = JSON.parse(await getJSON());
        console.log(data);
    } catch(e) {
        console.log(e);
    }
}

3. 條件

用async/ wait編寫條件代碼要簡(jiǎn)單得多:

function loadData() {
  return getJSON()
    .then(function(response) {
      if (response.needsAnotherRequest) {
        return makeAnotherRequest(response)
          .then(function(anotherResponse) {
            console.log(anotherResponse)
            return anotherResponse
          })
      } else {
        console.log(response)
        return response
      }
    })
}

async function loadData() {
  var response = await getJSON();
  if (response.needsAnotherRequest) {
    var anotherResponse = await makeAnotherRequest(response);
    console.log(anotherResponse)
    return anotherResponse
  } else {
    console.log(response);
    return response;    
  }
}

4. 錯(cuò)誤堆棧

與 async/await不同瓮床,從 Promise 鏈返回的錯(cuò)誤堆棧不提供錯(cuò)誤發(fā)生在哪里。看看下面這些

function loadData() {
  return callAPromise()
    .then(callback1)
    .then(callback2)
    .then(callback3)
    .then(() => {
      throw new Error("boom");
    })
}
loadData()
  .catch(function(e) {
    console.log(err);
// Error: boom at callAPromise.then.then.then.then (index.js:8:13)
});

與:

async function loadData() {
  await callAPromise1()
  await callAPromise2()
  await callAPromise3()
  await callAPromise4()
  await callAPromise5()
  throw new Error("boom");
}
loadData()
  .catch(function(e) {
    console.log(err);
    // output
    // Error: boom at loadData (index.js:7:9)
});

5. 調(diào)試

如果你使用過 Promise 隘庄,那么你知道調(diào)試它們是一場(chǎng)噩夢(mèng)踢步。例如,如果在一個(gè)程序中設(shè)置了一個(gè)斷點(diǎn)丑掺,然后阻塞并使用調(diào)試快捷方式(如“停止”)获印,調(diào)試器將不會(huì)移動(dòng)到下面,因?yàn)樗弧爸鸩健眻?zhí)行同步代碼街州。使用async/wait兼丰,您可以逐步完成wait調(diào)用,就像它們是正常的同步函數(shù)一樣菇肃。

編輯中可能存在的bug沒法實(shí)時(shí)知道,事后為了解決這些bug,花了大量的時(shí)間進(jìn)行l(wèi)og 調(diào)試取募,這邊順便給大家推薦一個(gè)好用的BUG監(jiān)控工具Fundebug琐谤。

原文:https://blog.sessionstack.com...

關(guān)于Fundebug

Fundebug專注于JavaScript、微信小程序玩敏、微信小游戲斗忌、支付寶小程序、React Native旺聚、Node.js和Java線上應(yīng)用實(shí)時(shí)BUG監(jiān)控织阳。 自從2016年雙十一正式上線,F(xiàn)undebug累計(jì)處理了10億+錯(cuò)誤事件砰粹,付費(fèi)客戶有Google唧躲、360、金山軟件碱璃、百姓網(wǎng)等眾多品牌企業(yè)弄痹。歡迎大家免費(fèi)試用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嵌器,一起剝皮案震驚了整個(gè)濱河市肛真,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌爽航,老刑警劉巖蚓让,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異讥珍,居然都是意外死亡历极,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門衷佃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來执解,“玉大人,你說我怎么就攤上這事∷ル纾” “怎么了新蟆?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)右蕊。 經(jīng)常有香客問我琼稻,道長(zhǎng),這世上最難降的妖魔是什么饶囚? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任帕翻,我火速辦了婚禮,結(jié)果婚禮上萝风,老公的妹妹穿的比我還像新娘嘀掸。我一直安慰自己,他們只是感情好规惰,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布睬塌。 她就那樣靜靜地躺著,像睡著了一般歇万。 火紅的嫁衣襯著肌膚如雪揩晴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天贪磺,我揣著相機(jī)與錄音硫兰,去河邊找鬼。 笑死寒锚,一個(gè)胖子當(dāng)著我的面吹牛劫映,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播刹前,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼苏研,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了腮郊?” 一聲冷哼從身側(cè)響起摹蘑,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎轧飞,沒想到半個(gè)月后衅鹿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡过咬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年大渤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掸绞。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泵三,死狀恐怖耕捞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情烫幕,我是刑警寧澤俺抽,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站较曼,受9級(jí)特大地震影響磷斧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捷犹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一弛饭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧萍歉,春花似錦侣颂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至销凑,卻和暖如春丛晌,著一層夾襖步出監(jiān)牢的瞬間仅炊,已是汗流浹背斗幼。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抚垄,地道東北人蜕窿。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像呆馁,于是被迫代替她去往敵國(guó)和親桐经。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容