從event loop到async await來了解事件循環(huán)機(jī)制

JS為什么是單線程的?

最初設(shè)計JS是用來在瀏覽器驗(yàn)證表單操控DOM元素的是一門腳本語言验庙,如果js是多線程的那么兩個線程同時對一個DOM元素進(jìn)行了相互沖突的操作幌墓,那么瀏覽器的解析器是無法執(zhí)行的。

JS為什么需要異步?

如果JS中不存在異步特笋,只能自上而下執(zhí)行黔姜,如果上一行解析時間很長拢切,那么下面的代碼就會被阻塞。對于用戶而言秆吵,阻塞就意味著"卡死"淮椰,這樣就導(dǎo)致了很差的用戶體驗(yàn)。比如在進(jìn)行ajax請求的時候如果沒有返回數(shù)據(jù)后面的代碼就沒辦法執(zhí)行。

JS單線程又是如何實(shí)現(xiàn)異步的呢?

js中的異步以及多線程都可以理解成為一種“假象”主穗,就拿h5的WebWorker來說泻拦,子線程有諸多限制,不能控制DOM元素忽媒、不能修改全局對象 等等争拐,通常只用來做計算做數(shù)據(jù)處理。這些限制并沒有違背我們之前的觀點(diǎn)晦雨,所以說是“假象”架曹。JS異步的執(zhí)行機(jī)制其實(shí)就是事件循環(huán)(eventloop),理解了eventloop機(jī)制闹瞧,就理解了JS異步的執(zhí)行機(jī)制绑雄。

JS的事件循環(huán)(eventloop)是怎么運(yùn)作的?

事件循環(huán)奥邮、eventloop万牺、運(yùn)行機(jī)制 這三個術(shù)語其實(shí)說的是同一個東西,在寫這篇文章之前我一直以為事件循環(huán)簡單的很洽腺,就是先執(zhí)行同步操作脚粟,然后把異步操作排在事件隊列里,等同步操作都運(yùn)行完了(運(yùn)行椪号螅空閑)核无,按順序運(yùn)行事件隊列里的內(nèi)容。但是遠(yuǎn)不止這么膚淺度液,我們接下來一步一步的深入來了解厕宗。

“先執(zhí)行同步操作異步操作排在事件隊列里”這樣的理解其實(shí)也沒有任何問題但如果深入的話會引出來很多其他概念,比如event table和event queue堕担,我們來看運(yùn)行過程:

1.首先判斷JS是同步還是異步,同步就進(jìn)入主線程運(yùn)行曲聂,異步就進(jìn)入event table霹购。
2.異步任務(wù)在event table中注冊事件,當(dāng)滿足觸發(fā)條件后(觸發(fā)條件可能是延時也可能是ajax回調(diào))朋腋,被推入event queue齐疙。
3.同步任務(wù)進(jìn)入主線程后一直執(zhí)行,直到主線程空閑時旭咽,才會去event queue中查看是否有可執(zhí)行的異步任務(wù)贞奋,如果有就推入主線程中。

setTimeout(() => {
  console.log('2秒到了')
}, 2000)

我們用上面的第二條來分析一下這段腳本穷绵,setTimeout是異步操作首先進(jìn)入event table轿塔,注冊的事件就是他的回調(diào),觸發(fā)條件就是2秒之后,當(dāng)滿足條件回調(diào)被推入event queue勾缭,當(dāng)主線程空閑時會去event queue里查看是否有可執(zhí)行的任務(wù)揍障。

console.log(1) // 同步任務(wù)進(jìn)入主線程
setTimeout(fun(),0)   // 異步任務(wù),被放入event table俩由, 0秒之后被推入event queue里
console.log(3) // 同步任務(wù)進(jìn)入主線程

1毒嫡、3是同步任務(wù)馬上會被執(zhí)行,執(zhí)行完成之后主線程空閑去event queue(事件隊列)里查看是否有任務(wù)在等待執(zhí)行幻梯,這就是為什么setTimeout的延遲時間是0毫秒?yún)s在最后執(zhí)行的原因兜畸。

關(guān)于setTimeout有一點(diǎn)要注意延時的時間有時候并不是那么準(zhǔn)確。

setTimeout(() => {
  console.log('2秒到了')
}, 2000)
wait(9999999999)

分析運(yùn)行過程:
1.console進(jìn)入Event Table并注冊碘梢,計時開始咬摇。
2.執(zhí)行sleep函數(shù),sleep方法雖然是同步任務(wù)但sleep方法進(jìn)行了大量的邏輯運(yùn)算痘系,耗時超過了2秒菲嘴。
3.2秒到了,計時事件timeout完成汰翠,console進(jìn)入Event Queue龄坪,但是sleep還沒執(zhí)行完,主線程還被占用复唤,只能等著健田。
4.sleep終于執(zhí)行完了,console終于從Event Queue進(jìn)入了主線程執(zhí)行佛纫,這個時候已經(jīng)遠(yuǎn)遠(yuǎn)超過了2秒妓局。

其實(shí)延遲2秒只是表示2秒后,setTimeout里的函數(shù)被會推入event queue呈宇,而event queue(事件隊列)里的任務(wù)好爬,只有在主線程空閑時才會執(zhí)行。上述的流程走完甥啄,我們知道setTimeout這個函數(shù)存炮,是經(jīng)過指定時間后,把要執(zhí)行的任務(wù)(本例中為console)加入到Event Queue中蜈漓,又因?yàn)槭菃尉€程任務(wù)要一個一個執(zhí)行穆桂,如果前面的任務(wù)需要的時間太久,那么只能等著融虽,導(dǎo)致真正的延遲時間遠(yuǎn)遠(yuǎn)大于2秒享完。
我們還經(jīng)常遇到setTimeout(fn,0)這樣的代碼有额,它的含義是般又,指定某個任務(wù)在主線程最早的空閑時間執(zhí)行彼绷,意思就是不用再等多少秒了,只要主線程執(zhí)行棧內(nèi)的同步任務(wù)全部執(zhí)行完成倒源,棧為空就馬上執(zhí)行苛预。但是即便主線程為空,0毫秒實(shí)際上也是達(dá)不到的笋熬。根據(jù)HTML的標(biāo)準(zhǔn)热某,最低是4毫秒。

關(guān)于setInterval:
以setInterval(fn胳螟,ms)為例昔馋,setInterval是循環(huán)執(zhí)行的,setInterval會每隔指定的時間將注冊的函數(shù)置入Event Queue糖耸,不是每過ms秒會執(zhí)行一次fn秘遏,而是每過ms秒,會有fn進(jìn)入Event Queue嘉竟。需要注意的一點(diǎn)是邦危,一旦setInterval的回調(diào)函數(shù)fn執(zhí)行時間超過了延遲時間ms,那么就完全看不出來有時間間隔了舍扰。

上面的概念很基礎(chǔ)也很容易理解但不幸的消息是上面講的一切都不是絕對的正確倦蚪,因?yàn)樯婕暗絇romise、async/await边苹、process.nextTick(node)所以要對任務(wù)有更精細(xì)的定義:

宏任務(wù)(macro-task):包括整體代碼script陵且,setTimeout,setInterval个束。
微任務(wù)(micro-task):Promise慕购,process.nextTick。

在劃分宏任務(wù)茬底、微任務(wù)的時候并沒有提到async/await因?yàn)閍sync/await的本質(zhì)就是Promise沪悲。

事件循環(huán)機(jī)制到底是怎么樣的?
不同類型的任務(wù)會進(jìn)入對應(yīng)的Event Queue阱表,比如setTimeout和setInterval會進(jìn)入相同(宏任務(wù))的Event Queue可训。而Promise和process.nextTick會進(jìn)入相同(微任務(wù))的Event Queue。

1.「宏任務(wù)」捶枢、「微任務(wù)」都是隊列,一段代碼執(zhí)行時飞崖,會先執(zhí)行宏任務(wù)中的同步代碼烂叔。
2.進(jìn)行第一輪事件循環(huán)的時候會把全部的js腳本當(dāng)成一個宏任務(wù)來運(yùn)行。
3.如果執(zhí)行中遇到setTimeout之類宏任務(wù)固歪,那么就把這個setTimeout內(nèi)部的函數(shù)推入「宏任務(wù)的隊列」中蒜鸡,下一輪宏任務(wù)執(zhí)行時調(diào)用胯努。
4.如果執(zhí)行中遇到 promise.then() 之類的微任務(wù),就會推入到「當(dāng)前宏任務(wù)的微任務(wù)隊列」中逢防,在本輪宏任務(wù)的同步代碼都執(zhí)行完成后叶沛,依次執(zhí)行所有的微任務(wù)。
5.第一輪事件循環(huán)中當(dāng)執(zhí)行完全部的同步腳本以及微任務(wù)隊列中的事件忘朝,這一輪事件循環(huán)就結(jié)束了灰署,開始第二輪事件循環(huán)。
6.第二輪事件循環(huán)同理先執(zhí)行同步腳本局嘁,遇到其他宏任務(wù)代碼塊繼續(xù)追加到「宏任務(wù)的隊列」中溉箕,遇到微任務(wù),就會推入到「當(dāng)前宏任務(wù)的微任務(wù)隊列」中悦昵,在本輪宏任務(wù)的同步代碼執(zhí)行都完成后肴茄,依次執(zhí)行當(dāng)前所有的微任務(wù)。
7.開始第三輪但指,循環(huán)往復(fù)...

下面用代碼來深入理解上面的機(jī)制:

setTimeout(function() {
    console.log('4')
})

new Promise(function(resolve) {
    console.log('1') // 同步任務(wù)
    resolve()
}).then(function() {
    console.log('3')
})
console.log('2')

1.這段代碼作為宏任務(wù)寡痰,進(jìn)入主線程。
2.先遇到setTimeout棋凳,那么將其回調(diào)函數(shù)注冊后分發(fā)到宏任務(wù)Event Queue拦坠。
3.接下來遇到了Promise,new Promise立即執(zhí)行贫橙,then函數(shù)分發(fā)到微任務(wù)Event Queue贪婉。
4.遇到console.log(),立即執(zhí)行卢肃。
5.整體代碼script作為第一個宏任務(wù)執(zhí)行結(jié)束疲迂。查看當(dāng)前有沒有可執(zhí)行的微任務(wù),執(zhí)行then的回調(diào)莫湘。
(第一輪事件循環(huán)結(jié)束了尤蒿,我們開始第二輪循環(huán)。)
6.從宏任務(wù)Event Queue開始幅垮。我們發(fā)現(xiàn)了宏任務(wù)Event Queue中setTimeout對應(yīng)的回調(diào)函數(shù)腰池,立即執(zhí)行。
執(zhí)行結(jié)果:1 - 2 - 3 - 4

console.log('1')
setTimeout(function() {
    console.log('2')
    process.nextTick(function() {
        console.log('3')
    })
    new Promise(function(resolve) {
        console.log('4')
        resolve()
    }).then(function() {
        console.log('5')
    })
})

process.nextTick(function() {
    console.log('6')
})

new Promise(function(resolve) {
    console.log('7')
    resolve()
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9')
    process.nextTick(function() {
        console.log('10')
    })
    new Promise(function(resolve) {
        console.log('11')
        resolve()
    }).then(function() {
        console.log('12')
    })
})

1.整體script作為第一個宏任務(wù)進(jìn)入主線程忙芒,遇到console.log(1)輸出1示弓。
2.遇到setTimeout,其回調(diào)函數(shù)被分發(fā)到宏任務(wù)Event Queue中呵萨。我們暫且記為setTimeout1奏属。
3.遇到process.nextTick(),其回調(diào)函數(shù)被分發(fā)到微任務(wù)Event Queue中潮峦。我們記為process1囱皿。
4.遇到Promise勇婴,new Promise直接執(zhí)行,輸出7嘱腥。then被分發(fā)到微任務(wù)Event Queue中耕渴。我們記為then1。
5.又遇到了setTimeout齿兔,其回調(diào)函數(shù)被分發(fā)到宏任務(wù)Event Queue中橱脸,我們記為setTimeout2。
6.現(xiàn)在開始執(zhí)行微任務(wù)愧驱,我們發(fā)現(xiàn)了process1和then1兩個微任務(wù)慰技,執(zhí)行process1,輸出6。執(zhí)行then1组砚,輸出8吻商。
第一輪事件循環(huán)正式結(jié)束,這一輪的結(jié)果是輸出1糟红,7艾帐,6,8盆偿。那么第二輪事件循環(huán)從setTimeout1宏任務(wù)開始:
1.首先輸出2柒爸。接下來遇到了process.nextTick(),同樣將其分發(fā)到微任務(wù)Event Queue中事扭,記為process2捎稚。
2.new Promise立即執(zhí)行輸出4,then也分發(fā)到微任務(wù)Event Queue中求橄,記為then2今野。
3.現(xiàn)在開始執(zhí)行微任務(wù),我們發(fā)現(xiàn)有process2和then2兩個微任務(wù)可以執(zhí)行輸出3罐农,5条霜。
第二輪事件循環(huán)結(jié)束,第二輪輸出2涵亏,4宰睡,3,5气筋。第三輪事件循環(huán)從setTimeout2宏任務(wù)開始:
1.直接輸出9拆内,將process.nextTick()分發(fā)到微任務(wù)Event Queue中。記為process3宠默。
2.直接執(zhí)行new Promise矛纹,輸出11庞钢。將then分發(fā)到微任務(wù)Event Queue中螃诅,記為then3。
3.執(zhí)行兩個微任務(wù)process3和then3肴敛。輸出10艾君。輸出12采够。
第三輪事件循環(huán)結(jié)束,第三輪輸出9冰垄,11蹬癌,10,12虹茶。
整段代碼逝薪,共進(jìn)行了三次事件循環(huán),完整的輸出為1蝴罪,7董济,6,8要门,2虏肾,4,3欢搜,5封豪,9,11炒瘟,10吹埠,12。
(請注意疮装,node環(huán)境下的事件監(jiān)聽依賴libuv與前端環(huán)境不完全相同缘琅,輸出順序可能會有誤差)

new Promise(function (resolve) { 
    console.log('1')// 宏任務(wù)一
    resolve()
}).then(function () {
    console.log('3') // 宏任務(wù)一的微任務(wù)
})
setTimeout(function () { // 宏任務(wù)二
    console.log('4')
    setTimeout(function () { // 宏任務(wù)五
        console.log('7')
        new Promise(function (resolve) {
            console.log('8')
            resolve()
        }).then(function () {
            console.log('10')
            setTimeout(function () {  // 宏任務(wù)七
                console.log('12')
            })
        })
        console.log('9')
    })
})
setTimeout(function () { // 宏任務(wù)三
    console.log('5')
})
setTimeout(function () {  // 宏任務(wù)四
    console.log('6')
    setTimeout(function () { // 宏任務(wù)六
        console.log('11')
    })
})
console.log('2') // 宏任務(wù)一

1.全部的代碼作為第一個宏任務(wù)進(jìn)入主線程執(zhí)行。
2.首先輸出1斩个,是同步代碼胯杭。then回調(diào)作為微任務(wù)進(jìn)入到宏任務(wù)一的微任務(wù)隊列。
3.下面最外層的三個setTimeout分別是宏任務(wù)二受啥、宏任務(wù)三做个、宏任務(wù)四按序排入宏任務(wù)隊列。
4.輸出2滚局,現(xiàn)在宏任務(wù)一的同步代碼都執(zhí)行完成了接下來執(zhí)行宏任務(wù)一的微任務(wù)輸出3居暖。
第一輪事件循環(huán)完成了
5.現(xiàn)在執(zhí)行宏任務(wù)二輸出4,后面的setTimeout作為宏任務(wù)五排入宏任務(wù)隊列藤肢。
第二輪事件循環(huán)完成了
6.執(zhí)行宏任務(wù)三輸出5太闺,執(zhí)行宏任務(wù)四輸出6,宏任務(wù)四里面的setTimeout作為宏任務(wù)六嘁圈。
7.執(zhí)行宏任務(wù)五輸出7省骂,8蟀淮。then回調(diào)作為宏任務(wù)五的微任務(wù)排入宏任務(wù)五的微任務(wù)隊列。
8.輸出同步代碼9钞澳,宏任務(wù)五的同步代碼執(zhí)行完了怠惶,現(xiàn)在執(zhí)行宏任務(wù)五的微任務(wù)。
9.輸出10轧粟,后面的setTimeout作為宏任務(wù)七排入宏任務(wù)的隊列策治。
宏任務(wù)五執(zhí)行完成了,當(dāng)前已經(jīng)是第五輪事件循環(huán)了兰吟。
10.執(zhí)行宏任務(wù)六輸出11通惫,執(zhí)行宏任務(wù)七輸出12,

-^-混蔼,這個案例是有點(diǎn)惡心履腋,目的是讓大家明白各宏任務(wù)之間執(zhí)行的順序以及宏任務(wù)和微任務(wù)的執(zhí)行關(guān)系。

初步總結(jié):
宏任務(wù)是一個棧按先入先執(zhí)行的原則拄丰,微任務(wù)也是一個棧也是先入先執(zhí)行府树。
但是每個宏任務(wù)都對應(yīng)會有一個微任務(wù)棧,宏任務(wù)在執(zhí)行過程中會先執(zhí)行同步代碼再執(zhí)行微任務(wù)棧料按。

上面的案例只是用setTimeout和Promise模擬了一些場景來幫助理解奄侠,并沒有用到async/await下面我們從什么是async/await開始講起。

async/await是什么载矿?

我們創(chuàng)建了 promise 但不能同步等待它執(zhí)行完成垄潮。我們只能通過 then 傳一個回調(diào)函數(shù)這樣很容易再次陷入 promise 的回調(diào)地獄。實(shí)際上闷盔,async/await 在底層轉(zhuǎn)換成了 promise 和 then 回調(diào)函數(shù)弯洗。也就是說,這是 promise 的語法糖逢勾。每次我們使用 await, 解釋器都創(chuàng)建一個 promise 對象牡整,然后把剩下的 async 函數(shù)中的操作放到 then 回調(diào)函數(shù)中。async/await 的實(shí)現(xiàn)溺拱,離不開 Promise逃贝。從字面意思來理解,async 是“異步”的簡寫迫摔,而 await 是 async wait 的簡寫可以認(rèn)為是等待異步方法執(zhí)行完成沐扳。

async/await用來干什么?

用來優(yōu)化 promise 的回調(diào)問題句占,被稱作是異步的終極解決方案沪摄。

async/await內(nèi)部做了什么?

async 函數(shù)會返回一個 Promise 對象,如果在函數(shù)中 return 一個直接量(普通變量)杨拐,async 會把這個直接量通過 Promise.resolve() 封裝成 Promise 對象祈餐。如果你返回了promise那就以你返回的promise為準(zhǔn)。
await 是在等待戏阅,等待運(yùn)行的結(jié)果也就是返回值昼弟。await后面通常是一個異步操作(promise),但是這不代表 await 后面只能跟異步操作 await 后面實(shí)際是可以接普通函數(shù)調(diào)用或者直接量的奕筐。

await的等待機(jī)制?

如果 await 后面跟的不是一個 Promise变骡,那 await 后面表達(dá)式的運(yùn)算結(jié)果就是它等到的東西离赫;如果 await 后面跟的是一個 Promise 對象,await 它會“阻塞”后面的代碼塌碌,等著 Promise 對象 resolve渊胸,然后得到 resolve 的值作為 await 表達(dá)式的運(yùn)算結(jié)果。但是此“阻塞”非彼“阻塞”這就是 await 必須用在 async 函數(shù)中的原因台妆。async 函數(shù)調(diào)用不會造成“阻塞”翎猛,它內(nèi)部所有的“阻塞”都被封裝在一個 Promise 對象中異步執(zhí)行。(這里的阻塞理解成異步等待更合理)

async/await在使用過程中有什么規(guī)定接剩?

每個 async 方法都返回一個 promise 對象切厘。await 只能出現(xiàn)在 async 函數(shù)中。

async/await 在什么場景使用懊缺?

單一的 Promise 鏈并不能發(fā)現(xiàn) async/await 的優(yōu)勢疫稿,但是如果需要處理由多個 Promise 組成的 then 鏈的時候,優(yōu)勢就能體現(xiàn)出來了(Promise 通過 then 鏈來解決多層回調(diào)的問題鹃两,現(xiàn)在又用 async/await 來進(jìn)一步優(yōu)化它)遗座。

async/await如何使用?

假設(shè)一個業(yè)務(wù)俊扳,分多個步驟完成途蒋,每個步驟都是異步的且依賴于上一個步驟的結(jié)果。

function myPromise(n) {
    return new Promise(resolve => {
        console.log(n)
        setTimeout(() => resolve(n+1), n)
    })
}
function step1(n) {
    return myPromise(n)
}
function step2(n) {
    return myPromise(n)
}
function step3(n) {
    return myPromise(n)
}

如果用 Promise 實(shí)現(xiàn)
step1(1000)
.then(a => step2(a))
.then(b => step3(b))
.then(result => {
    console.log(result)
})

如果用 async/await 來實(shí)現(xiàn)呢
async function myResult() {
    const a = await step1(1000)
    const b = await step2(a)
    const result = await step3(b)
    return result
}
myResult().then(result => {
    console.log(result)
}).catch(err => {
    // 如果myResult內(nèi)部有語法錯誤會觸發(fā)catch方法
})

看的出來async/await的寫法更加優(yōu)雅一些要比Promise的鏈?zhǔn)秸{(diào)用更加直觀也易于維護(hù)馋记。

我們來看在任務(wù)隊列中async/await的運(yùn)行機(jī)制号坡,先給出大概方向再通過案例來證明:
1.async定義的是一個Promise函數(shù)和普通函數(shù)一樣只要不調(diào)用就不會進(jìn)入事件隊列。
2.async內(nèi)部如果沒有主動return Promise抗果,那么async會把函數(shù)的返回值用Promise包裝筋帖。
3.await關(guān)鍵字必須出現(xiàn)在async函數(shù)中,await后面不是必須要跟一個異步操作冤馏,也可以是一個普通表達(dá)式日麸。
4.遇到await關(guān)鍵字,await右邊的語句會被立即執(zhí)行然后await下面的代碼進(jìn)入等待狀態(tài),等待await得到結(jié)果代箭。
await后面如果不是 promise 對象, await會阻塞后面的代碼墩划,先執(zhí)行async外面的同步代碼,同步代碼執(zhí)行完嗡综,再回到async內(nèi)部乙帮,把這個非promise的東西,作為 await表達(dá)式的結(jié)果极景。
await后面如果是 promise 對象察净,await 也會暫停async后面的代碼,先執(zhí)行async外面的同步代碼盼樟,等著 Promise 對象 fulfilled氢卡,然后把 resolve 的參數(shù)作為 await 表達(dá)式的運(yùn)算結(jié)果。

setTimeout(function () {
  console.log('6')
}, 0)
console.log('1')
async function async1() {
  console.log('2')
  await async2()
  console.log('5')
}
async function async2() {
  console.log('3')
}
async1()
console.log('4')

1.6是宏任務(wù)在下一輪事件循環(huán)執(zhí)行
2.先同步輸出1晨缴,然后調(diào)用了async1()译秦,輸出2。
3.await async2() 會先運(yùn)行async2()击碗,5進(jìn)入等待狀態(tài)筑悴。
4.輸出3,這個時候先執(zhí)行async函數(shù)外的同步代碼輸出4稍途。
5.最后await拿到等待的結(jié)果繼續(xù)往下執(zhí)行輸出5阁吝。
6.進(jìn)入第二輪事件循環(huán)輸出6。

console.log('1')
async function async1() {
  console.log('2')
  await 'await的結(jié)果'
  console.log('5')
}

async1()
console.log('3')

new Promise(function (resolve) {
  console.log('4')
  resolve()
}).then(function () {
  console.log('6')
})

1.首先輸出1晰房,然后進(jìn)入async1()函數(shù)求摇,輸出2。
2.await后面雖然是一個直接量殊者,但是還是會先執(zhí)行async函數(shù)外的同步代碼与境。
3.輸出3,進(jìn)入Promise輸出4猖吴,then回調(diào)進(jìn)入微任務(wù)隊列摔刁。
4.現(xiàn)在同步代碼執(zhí)行完了,回到async函數(shù)繼續(xù)執(zhí)行輸出5海蔽。
5.最后運(yùn)行微任務(wù)輸出6共屈。

async function async1() {
  console.log('2')
  await async2()
  console.log('7')
}

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

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

console.log('1')
async1()

new Promise(function (resolve) {
  console.log('4')
  resolve()
}).then(function () {
  console.log('6')
})
console.log('5')

1.首先輸出同步代碼1,然后進(jìn)入async1方法輸出2党窜。
2.因?yàn)橛龅絘wait所以先進(jìn)入async2方法拗引,后面的7處于等待狀態(tài)。
3.在async2中輸出3幌衣,現(xiàn)在跳出async函數(shù)先執(zhí)行外面的同步代碼矾削。
4.輸出4壤玫,5。then回調(diào)進(jìn)入微任務(wù)棧哼凯。
5.現(xiàn)在宏任務(wù)執(zhí)行完了欲间,執(zhí)行微任務(wù)輸出6。
6.然后回到async1函數(shù)接著往下執(zhí)行輸出7断部。

setTimeout(function () {
  console.log('9')
}, 0)
console.log('1')
async function async1() {
  console.log('2')
  await async2()
  console.log('8')
}
async function async2() {
  return new Promise(function (resolve) {
    console.log('3')
    resolve()
  }).then(function () {
    console.log('6')
  })
}
async1()

new Promise(function (resolve) {
  console.log('4')
  resolve()
}).then(function () {
  console.log('7')
})
console.log('5')

1.先輸出1猎贴,2,3蝴光。3后面的then進(jìn)入微任務(wù)隊列她渴。
2.執(zhí)行外面的同步代碼,輸出4蔑祟,5惹骂。4后面的then進(jìn)入微任務(wù)隊列。
4.接下來執(zhí)行微任務(wù)做瞪,因?yàn)?后面的then先進(jìn)入,所以按序輸出6右冻,7装蓬。
5.下面回到async1函數(shù),await關(guān)鍵字等到了結(jié)果繼續(xù)往下執(zhí)行纱扭。
6.輸出8牍帚,進(jìn)行下一輪事件循環(huán)也就是宏任務(wù)二,輸出9乳蛾。

async function async1() {
  console.log('2')
  const data = await async2()
  console.log(data)
  console.log('8')
}

async function async2() {
  return new Promise(function (resolve) {
    console.log('3')
    resolve('await的結(jié)果')
  }).then(function (data) {
    console.log('6')
    return data
  })
}
console.log('1')

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

async1()

new Promise(function (resolve) {
  console.log('4')
  resolve()
}).then(function () {
  console.log('7')
})
console.log('5')

1.函數(shù)async1和async2只是定義先不去管他暗赶,首先輸出1。
2.setTimeout作為宏任務(wù)進(jìn)入宏任務(wù)隊列等待下一輪事件循環(huán)肃叶。
3.進(jìn)入async1()函數(shù)輸出2蹂随,await下面的代碼進(jìn)入等待狀態(tài)。
4.進(jìn)入async2()輸出3因惭,then回調(diào)進(jìn)入微任務(wù)隊列岳锁。
5.現(xiàn)在執(zhí)行外面的同步代碼,輸出4蹦魔,5激率,then回調(diào)進(jìn)入微任務(wù)隊列。
6.按序執(zhí)行微任務(wù)勿决,輸出6乒躺,7。現(xiàn)在回到async1函數(shù)低缩。
7.輸出data嘉冒,也就是await關(guān)鍵字等到的內(nèi)容,接著輸出8。
8.進(jìn)行下一輪時間循環(huán)輸出9健爬。
執(zhí)行結(jié)果:1 - 2 - 3 - 4 - 5 - 6 - 7 - await的結(jié)果 - 8 - 9

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

async function async1() {
  console.log('1')
  const data = await async2()
  console.log('6')
  return data
}

async function async2() {
  return new Promise(resolve => {
    console.log('2')
    resolve('async2的結(jié)果')
  }).then(data => {
    console.log('4')
    return data
  })
}

async1().then(data => {
  console.log('7')
  console.log(data)
})

new Promise(function (resolve) {
  console.log('3')
  resolve()
}).then(function () {
  console.log('5')
})

1.setTimeout作為宏任務(wù)進(jìn)入宏任務(wù)隊列等待下一輪事件循環(huán)控乾。
2.先執(zhí)行async1函數(shù),輸出1娜遵,6進(jìn)入等待狀態(tài)蜕衡,現(xiàn)在執(zhí)行async2。
3.輸出2设拟,then回調(diào)進(jìn)入微任務(wù)隊列慨仿。
4.接下來執(zhí)行外面的同步代碼輸出3,then回調(diào)進(jìn)入微任務(wù)隊列纳胧。
5.按序執(zhí)行微任務(wù)镰吆,輸出4,5跑慕。下面回到async1函數(shù)万皿。
6.輸出了4之后執(zhí)行了return data,await拿到了內(nèi)容核行。
7.繼續(xù)執(zhí)行輸出6牢硅,執(zhí)行了后面的 return data 才觸發(fā)了async1()的then回調(diào)輸出7以及data。
8.進(jìn)行第二輪事件循環(huán)輸出8芝雪。
執(zhí)行結(jié)果:1 - 2 - 3 -4 - 5 - 6 - 7 - async2的結(jié)果 - 8

案例有點(diǎn)多主要為了以后回顧减余,如果大家覺得我的理解有偏差歡迎指正。

緩緩先...

-^-

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惩系,一起剝皮案震驚了整個濱河市位岔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌堡牡,老刑警劉巖抒抬,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異悴侵,居然都是意外死亡瞧剖,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門可免,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抓于,“玉大人,你說我怎么就攤上這事浇借∽酱椋” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵妇垢,是天一觀的道長巾遭。 經(jīng)常有香客問我肉康,道長,這世上最難降的妖魔是什么灼舍? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任吼和,我火速辦了婚禮,結(jié)果婚禮上骑素,老公的妹妹穿的比我還像新娘炫乓。我一直安慰自己,他們只是感情好献丑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布末捣。 她就那樣靜靜地躺著,像睡著了一般创橄。 火紅的嫁衣襯著肌膚如雪箩做。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天妥畏,我揣著相機(jī)與錄音邦邦,去河邊找鬼。 笑死醉蚁,一個胖子當(dāng)著我的面吹牛圃酵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播馍管,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼薪韩!你這毒婦竟也來了确沸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤俘陷,失蹤者是張志新(化名)和其女友劉穎罗捎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拉盾,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡桨菜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了捉偏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倒得。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖夭禽,靈堂內(nèi)的尸體忽然破棺而出霞掺,到底是詐尸還是另有隱情,我是刑警寧澤讹躯,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布菩彬,位于F島的核電站缠劝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏骗灶。R本人自食惡果不足惜惨恭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望耙旦。 院中可真熱鬧脱羡,春花似錦、人聲如沸母廷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽琴昆。三九已至氓鄙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間业舍,已是汗流浹背抖拦。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舷暮,地道東北人态罪。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像下面,于是被迫代替她去往敵國和親复颈。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355

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

  • 弄懂js異步 講異步之前沥割,我們必須掌握一個基礎(chǔ)知識-event-loop耗啦。 我們知道JavaScript的一大特點(diǎn)...
    DCbryant閱讀 2,712評論 0 5
  • 歡迎閱讀專門探索 JavaScript 及其構(gòu)建組件的系列文章的第四章。 在識別和描述核心元素的過程中机杜,我們還分享...
    OSC開源社區(qū)閱讀 1,154評論 1 10
  • 在上一篇文章 從進(jìn)程和線程了解瀏覽器的工作原理 中帜讲,我們已經(jīng)了解了瀏覽器的渲染流程,瀏覽器初次渲染完成后椒拗,接下來就...
    zouyang0921閱讀 644評論 0 1
  • 已看慣了太陽的東升西落似将,月亮的陰晴圓缺;習(xí)慣了春夏秋冬的冷暖蚀苛,世間萬物的改變在验;卻很難看淡人間的悲歡離合、情仇恩怨堵未,...
    暖小瘋閱讀 321評論 0 1
  • 從小就沒有父親的安德烈在自己16歲的那天終于從母親那里得到了一份屬于父親留給自己的遺產(chǎn)译红,那是一封有些泛黃的信件。 ...
    秦約取閱讀 454評論 2 2