8張圖讓你一步步看清 async/await 和 promise 的執(zhí)行順序

作者:ziwei3749
https://segmentfault.com/a/1190000017224799

測試一下自己有沒有必要看

所以我寫這個的文章,主要還是交流學(xué)習(xí)般又,如果您已經(jīng)清楚了eventloop/async/await/promise這些東西呢,可以 break 啦

有說的不對的地方,歡迎留言討論寞蚌,

那么還是先通過一道題自我檢測一下于游,是否有必要繼續(xù)看下去把。

其實(shí)呢忧勿,這是去年一道爛大街的「今日頭條」的面試題杉女。

我覺得這道題的關(guān)鍵,不僅是說出正確的打印順序鸳吸,更重要的能否說清楚每一個步驟熏挎,為什么這樣執(zhí)行。


    async function async1() {
        console.log( 'async1 start' )
        await async2()
        console.log( 'async1 end' )
    }

    async function async2() {
        console.log( 'async2' )
    }

    console.log( 'script start' )

    setTimeout( function () {
        console.log( 'setTimeout' )
    }, 0 )

    async1();

    new Promise( function ( resolve ) {
        console.log( 'promise1' )
        resolve();
    } ).then( function () {
        console.log( 'promise2' )
    } )

    console.log( 'script end' )

注:因?yàn)槭且坏狼岸嗣嬖囶}晌砾,所以答案是以瀏覽器的eventloop機(jī)制為準(zhǔn)的坎拐,在node平臺上運(yùn)行會有差異。

     script start
     async1 start
     async2
     promise1
     script end
     promise2
     async1 end
     setTimeout

如果你發(fā)現(xiàn)運(yùn)行結(jié)果跟自己想的一樣养匈,可以選擇跳過這篇文章啦哼勇,

或者如果你有興趣看看俺倆的理解有沒有區(qū)別,可以跳到后面的 「畫圖講解的部分」

需要具備的前置知識

  • promise的使用經(jīng)驗(yàn)
  • 瀏覽器端的eventloop

不過如果是對 ES7 的 async 不太熟悉呕乎,是沒關(guān)系的哈积担,因?yàn)檫@篇文章會詳解 async。

那么如果不具備這些知識呢猬仁,推薦幾篇我覺得講得比較清楚的文章

主要內(nèi)容

第1部分:對于async await的理解

我推薦的那篇文章谆膳,對 async/await 講得更詳細(xì)。不過我希望自己能更加精煉的幫你理解它們

這部分撮躁,主要會講解 3 點(diǎn)內(nèi)容

  • 1.async 做一件什么事情漱病?
  • 2.await 在等什么?
  • 3.await 等到之后把曼,做了一件什么事情杨帽?
  • 4.補(bǔ)充: async/await 比 promise有哪些優(yōu)勢?(回頭補(bǔ)充)

1.async 做一件什么事情嗤军?

一句話概括: 帶 async 關(guān)鍵字的函數(shù)注盈,它使得你的函數(shù)的返回值必定是 promise 對象

也就是

如果async關(guān)鍵字函數(shù)返回的不是promise,會自動用Promise.resolve()包裝

如果async關(guān)鍵字函數(shù)顯式地返回promise叙赚,那就以你返回的promise為準(zhǔn)

這是一個簡單的例子老客,可以看到 async 關(guān)鍵字函數(shù)和普通函數(shù)的返回值的區(qū)別

async function fn1(){
    return 123
}

function fn2(){
    return 123
}

console.log(fn1())
console.log(fn2())
Promise {<resolved>: 123}

123

所以你看,async 函數(shù)也沒啥了不起的震叮,以后看到帶有 async 關(guān)鍵字的函數(shù)也不用慌張胧砰,你就想它無非就是把return值包裝了一下,其他就跟普通函數(shù)一樣苇瓣。

關(guān)于async關(guān)鍵字還有那些要注意的尉间?

  • 在語義上要理解,async表示函數(shù)內(nèi)部有異步操作
  • 另外注意击罪,一般 await 關(guān)鍵字要在 async 關(guān)鍵字函數(shù)的內(nèi)部哲嘲,await 寫在外面會報(bào)錯。

2.await 在等什么媳禁?

一句話概括: await等的是右側(cè)「表達(dá)式」的結(jié)果

也就是說眠副,

右側(cè)如果是函數(shù),那么函數(shù)的return值就是「表達(dá)式的結(jié)果」

右側(cè)如果是一個 'hello' 或者什么值竣稽,那表達(dá)式的結(jié)果就是 'hello'

async function async1() {
    console.log( 'async1 start' )
    await async2()
    console.log( 'async1 end' )
}
async function async2() {
    console.log( 'async2' )
}
async1()
console.log( 'script start' )

這里注意一點(diǎn)侦啸,可能大家都知道await會讓出線程,阻塞后面的代碼丧枪,那么上面例子中, 'async2' 和 'script start' 誰先打印呢庞萍?

是從左向右執(zhí)行拧烦,一旦碰到await直接跳出, 阻塞async2()的執(zhí)行?

還是從右向左钝计,先執(zhí)行async2后恋博,發(fā)現(xiàn)有await關(guān)鍵字齐佳,于是讓出線程,阻塞代碼呢债沮?

實(shí)踐的結(jié)論是炼吴,從右向左的。先打印async2疫衩,后打印的script start

之所以提一嘴硅蹦,是因?yàn)槲医?jīng)常看到這樣的說法闷煤,「一旦遇到await就立刻讓出線程童芹,阻塞后面的代碼」

這樣的說法,會讓我誤以為鲤拿,await后面那個函數(shù)假褪, async2()也直接被阻塞呢。

3.await 等到之后近顷,做了一件什么事情生音?

那么右側(cè)表達(dá)式的結(jié)果,就是await要等的東西窒升。

等到之后缀遍,對于await來說,分2個情況

  • 不是promise對象
  • 是promise對象

如果不是 promise , await會阻塞后面的代碼异剥,先執(zhí)行async外面的同步代碼瑟由,同步代碼執(zhí)行完,再回到async內(nèi)部冤寿,把這個非promise的東西歹苦,作為 await表達(dá)式的結(jié)果

如果它等到的是一個 promise 對象,await 也會暫停async后面的代碼督怜,先執(zhí)行async外面的同步代碼殴瘦,等著 Promise 對象 fulfilled,然后把 resolve 的參數(shù)作為 await 表達(dá)式的運(yùn)算結(jié)果号杠。

第2部分:畫圖一步步看清宏任務(wù)蚪腋、微任務(wù)的執(zhí)行過程

我們以開篇的經(jīng)典面試題為例,分析這個例子中的宏任務(wù)和微任務(wù)姨蟋。

        async function async1() {
            console.log( 'async1 start' )
            await async2()
            console.log( 'async1 end' )
        }
        async function async2() {
            console.log( 'async2' )
        }
        console.log( 'script start' )
        setTimeout( function () {
            console.log( 'setTimeout' )
        }, 0 )
        async1();
        new Promise( function ( resolve ) {
            console.log( 'promise1' )
            resolve();
        } ).then( function () {
            console.log( 'promise2' )
        } )
        console.log( 'script end' )

先分享一個我個人理解的宏任務(wù)和微任務(wù)的慨念屉凯,在我腦海中宏任務(wù)和為微任務(wù)如圖所示

也就是「宏任務(wù)」、「微任務(wù)」都是隊(duì)列眼溶。

一段代碼執(zhí)行時悠砚,會先執(zhí)行宏任務(wù)中的同步代碼,

  • 如果執(zhí)行中遇到setTimeout之類宏任務(wù)堂飞,那么就把這個setTimeout內(nèi)部的函數(shù)推入「宏任務(wù)的隊(duì)列」中灌旧,下一輪宏任務(wù)執(zhí)行時調(diào)用绑咱。
  • 如果執(zhí)行中遇到promise.then()之類的微任務(wù),就會推入到「當(dāng)前宏任務(wù)的微任務(wù)隊(duì)列」中枢泰,在本輪宏任務(wù)的同步代碼執(zhí)行都完成后描融,依次執(zhí)行所有的微任務(wù)1、2衡蚂、3

下面就以面試題為例子窿克,分析這段代碼的執(zhí)行順序.

每次宏任務(wù)和微任務(wù)發(fā)生變化,我都會畫一個圖來表示他們的變化讳窟。

直接打印同步代碼 console.log('script start')

首先是2個函數(shù)聲明让歼,雖然有async關(guān)鍵字,但不是調(diào)用我們就不看丽啡。然后首先是打印同步代碼 console.log('script start')

將setTimeout放入宏任務(wù)隊(duì)列

默認(rèn)<script></script>所包裹的代碼谋右,其實(shí)可以理解為是第一個宏任務(wù),所以這里是宏任務(wù)2

調(diào)用async1补箍,打印 同步代碼 console.log( 'async1 start' )

我們說過看到帶有async關(guān)鍵字的函數(shù)改执,不用害怕,它的僅僅是把return值包裝成了promise坑雅,其他并沒有什么不同的地方辈挂。所以就很普通的打印 console.log( 'async1 start' )

分析一下 await async2()

前文提過await,1.它先計(jì)算出右側(cè)的結(jié)果裹粤,2.然后看到await后终蒂,中斷async函數(shù)

- 先得到await右側(cè)表達(dá)式的結(jié)果。執(zhí)行async2()遥诉,打印同步代碼console.log('async2'), 并且return Promise.resolve(undefined)
- await后拇泣,中斷async函數(shù),先執(zhí)行async外的同步代碼

目前就直接打印 console.log('async2')

被阻塞后矮锈,要執(zhí)行async之外的代碼

執(zhí)行new Promise()霉翔,Promise構(gòu)造函數(shù)是直接調(diào)用的同步代碼,所以 console.log( 'promise1' )

代碼運(yùn)行到promise.then()

代碼運(yùn)行到promise.then()苞笨,發(fā)現(xiàn)這個是微任務(wù)债朵,所以暫時不打印,只是推入當(dāng)前宏任務(wù)的微任務(wù)隊(duì)列中瀑凝。

注意:這里只是把promise2推入微任務(wù)隊(duì)列序芦,并沒有執(zhí)行。微任務(wù)會在當(dāng)前宏任務(wù)的同步代碼執(zhí)行完畢粤咪,才會依次執(zhí)行

打印同步代碼 console.log( 'script end' )

沒什么好說的芝加。執(zhí)行完這個同步代碼后,「async外的代碼」終于走了一遍

下面該回到 await 表達(dá)式那里,執(zhí)行await Promise.resolve(undefined)了

回到async內(nèi)部藏杖,執(zhí)行await Promise.resolve(undefined)

這部分可能不太好理解,我盡量表達(dá)我的想法脉顿。

對于 await Promise.resolve(undefined) 如何理解呢蝌麸?

https://developer.mozilla.org...

根據(jù) MDN 原話我們知道

如果一個 Promise 被傳遞給一個 await 操作符,await 將等待 Promise 正常處理完成并返回其處理結(jié)果艾疟。

在我們這個例子中来吩,就是Promise.resolve(undefined)正常處理完成,并返回其處理結(jié)果蔽莱。那么await async2()就算是執(zhí)行結(jié)束了弟疆。

目前這個promise的狀態(tài)是fulfilled,等其處理結(jié)果返回就可以執(zhí)行await下面的代碼了盗冷。

那何時能拿到處理結(jié)果呢怠苔?

回憶平時我們用promise,調(diào)用resolve后仪糖,何時能拿到處理結(jié)果柑司?是不是需要在then的第一個參數(shù)里,才能拿到結(jié)果锅劝。

(調(diào)用resolve時攒驰,會把then的參數(shù)推入微任務(wù)隊(duì)列,等主線程空閑時故爵,再調(diào)用它)

所以這里的 await Promise.resolve() 就類似于

Promise.resolve(undefined).then((undefined) => {

})

把then的第一個回調(diào)參數(shù) (undefined) => {} 推入微任務(wù)隊(duì)列玻粪。

then執(zhí)行完,才是await async2()執(zhí)行結(jié)束诬垂。

await async2()執(zhí)行結(jié)束劲室,才能繼續(xù)執(zhí)行后面的代碼

如圖

此時當(dāng)前宏任務(wù)1都執(zhí)行完了,要處理微任務(wù)隊(duì)列里的代碼剥纷。

微任務(wù)隊(duì)列痹籍,先進(jìn)先出的原則,

  • 執(zhí)行微任務(wù)1晦鞋,打印promise2
  • 執(zhí)行微任務(wù)2蹲缠,沒什么內(nèi)容..

但是微任務(wù)2執(zhí)行后,await async2()語句結(jié)束悠垛,后面的代碼不再被阻塞线定,所以打印

console.log( 'async1 end' )

宏任務(wù)1執(zhí)行完成后,執(zhí)行宏任務(wù)2

宏任務(wù)2的執(zhí)行比較簡單,就是打印

console.log('setTimeout')

補(bǔ)充在不同瀏覽器上的測試結(jié)果

谷歌瀏覽器确买,目前是版本是「版本 71.0.3578.80(正式版本) (64 位)」 Mac操作系統(tǒng)

Safari瀏覽器的測試結(jié)果

火狐瀏覽器的測試結(jié)果

如果不理解可以留言斤讥,有錯誤的話也歡迎指正。

關(guān)于執(zhí)行順序

評論區(qū)有指出

  • Chrome72 dev版本的執(zhí)行順序是Promise2后打印,
  • 或者是babel編譯過后的代碼是promise2后打印芭商。

我自己也實(shí)踐了一下babel編譯后的代碼執(zhí)行順序的確是promise2后打印的..

原因是ESMA最新規(guī)范的有修改派草,然后這一點(diǎn)的詳情,說實(shí)話我目前也不是很清楚铛楣,評論區(qū)有給出資料近迁,可供參考討論。

https://github.com/rhinel/blo...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末簸州,一起剝皮案震驚了整個濱河市鉴竭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌岸浑,老刑警劉巖懦砂,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缴渊,死亡現(xiàn)場離奇詭異润歉,居然都是意外死亡足陨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門兵钮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛆橡,“玉大人,你說我怎么就攤上這事掘譬√┭荩” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵葱轩,是天一觀的道長睦焕。 經(jīng)常有香客問我,道長靴拱,這世上最難降的妖魔是什么垃喊? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮袜炕,結(jié)果婚禮上本谜,老公的妹妹穿的比我還像新娘。我一直安慰自己偎窘,他們只是感情好乌助,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著陌知,像睡著了一般他托。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仆葡,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天赏参,我揣著相機(jī)與錄音,去河邊找鬼。 笑死把篓,一個胖子當(dāng)著我的面吹牛纫溃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播韧掩,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼皇耗,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了揍很?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤万伤,失蹤者是張志新(化名)和其女友劉穎窒悔,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體敌买,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡简珠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了虹钮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片聋庵。...
    茶點(diǎn)故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖芙粱,靈堂內(nèi)的尸體忽然破棺而出祭玉,到底是詐尸還是另有隱情,我是刑警寧澤春畔,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布脱货,位于F島的核電站,受9級特大地震影響律姨,放射性物質(zhì)發(fā)生泄漏振峻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一择份、第九天 我趴在偏房一處隱蔽的房頂上張望扣孟。 院中可真熱鬧,春花似錦荣赶、人聲如沸凤价。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽料仗。三九已至,卻和暖如春伏蚊,著一層夾襖步出監(jiān)牢的瞬間立轧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留氛改,地道東北人帐萎。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像胜卤,于是被迫代替她去往敵國和親疆导。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評論 2 348

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

  • 我們在座的大部分都不是出身豪門的葛躏, 我們都要靠自己澈段, 所以我們要相信命運(yùn)給我們一個比別人低的起點(diǎn), 是想告訴我們讓...
    魚魚姐閱讀 231評論 0 0
  • 【Day10】今日閱讀《躍遷》舰攒,P259--P320(完)败富; 面對世界:專注而開放 高手戰(zhàn)略不是一種計(jì)謀,而且一種...
    XLDeng閱讀 229評論 1 1
  • 先生們摩窃,女士們兽叮,各位觀眾朋友們,又到了一周一次的牙齒治療日了猾愿,讓我們來看看鹦聪,今天會發(fā)生什么事情呢,下面為患者自述…...
    Depressed豬閱讀 561評論 0 51