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

JS為什么是單線程的?

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

JS為什么需要異步?

如果JS中不存在異步,只能自上而下執(zhí)行,如果上一行解析時(shí)間很長(zhǎng),那么下面的代碼就會(huì)被阻塞。對(duì)于用戶(hù)而言,阻塞就意味著"卡死",這樣就導(dǎo)致了很差的用戶(hù)體驗(yàn)。比如在進(jìn)行ajax請(qǐng)求的時(shí)候如果沒(méi)有返回?cái)?shù)據(jù)后面的代碼就沒(méi)辦法執(zhí)行。

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

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

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

事件循環(huán)、eventloop、運(yùn)行機(jī)制 這三個(gè)術(shù)語(yǔ)其實(shí)說(shuō)的是同一個(gè)東西,在寫(xiě)這篇文章之前我一直以為事件循環(huán)簡(jiǎn)單的很,就是先執(zhí)行同步操作,然后把異步操作排在事件隊(duì)列里,等同步操作都運(yùn)行完了(運(yùn)行??臻e),按順序運(yùn)行事件隊(duì)列里的內(nèi)容。但是遠(yuǎn)不止這么膚淺,我們接下來(lái)一步一步的深入來(lái)了解。

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

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

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

我們用上面的第二條來(lái)分析一下這段腳本,setTimeout是異步操作首先進(jìn)入event table,注冊(cè)的事件就是他的回調(diào),觸發(fā)條件就是2秒之后,當(dāng)滿(mǎn)足條件回調(diào)被推入event queue,當(dāng)主線程空閑時(shí)會(huì)去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ù)馬上會(huì)被執(zhí)行,執(zhí)行完成之后主線程空閑去event queue(事件隊(duì)列)里查看是否有任務(wù)在等待執(zhí)行,這就是為什么setTimeout的延遲時(shí)間是0毫秒?yún)s在最后執(zhí)行的原因。

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

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

分析運(yùn)行過(guò)程:
1.console進(jìn)入Event Table并注冊(cè),計(jì)時(shí)開(kāi)始。
2.執(zhí)行sleep函數(shù),sleep方法雖然是同步任務(wù)但sleep方法進(jìn)行了大量的邏輯運(yùn)算,耗時(shí)超過(guò)了2秒。
3.2秒到了,計(jì)時(shí)事件timeout完成,console進(jìn)入Event Queue,但是sleep還沒(méi)執(zhí)行完,主線程還被占用,只能等著。
4.sleep終于執(zhí)行完了,console終于從Event Queue進(jìn)入了主線程執(zhí)行,這個(gè)時(shí)候已經(jīng)遠(yuǎn)遠(yuǎn)超過(guò)了2秒。

其實(shí)延遲2秒只是表示2秒后,setTimeout里的函數(shù)被會(huì)推入event queue,而event queue(事件隊(duì)列)里的任務(wù),只有在主線程空閑時(shí)才會(huì)執(zhí)行。上述的流程走完,我們知道setTimeout這個(gè)函數(shù),是經(jīng)過(guò)指定時(shí)間后,把要執(zhí)行的任務(wù)(本例中為console)加入到Event Queue中,又因?yàn)槭菃尉€程任務(wù)要一個(gè)一個(gè)執(zhí)行,如果前面的任務(wù)需要的時(shí)間太久,那么只能等著,導(dǎo)致真正的延遲時(shí)間遠(yuǎn)遠(yuǎn)大于2秒。
我們還經(jīng)常遇到setTimeout(fn,0)這樣的代碼,它的含義是,指定某個(gè)任務(wù)在主線程最早的空閑時(shí)間執(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會(huì)每隔指定的時(shí)間將注冊(cè)的函數(shù)置入Event Queue,不是每過(guò)ms秒會(huì)執(zhí)行一次fn,而是每過(guò)ms秒,會(huì)有fn進(jìn)入Event Queue。需要注意的一點(diǎn)是,一旦setInterval的回調(diào)函數(shù)fn執(zhí)行時(shí)間超過(guò)了延遲時(shí)間ms,那么就完全看不出來(lái)有時(shí)間間隔了。

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

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

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

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

1.「宏任務(wù)」、「微任務(wù)」都是隊(duì)列,一段代碼執(zhí)行時(shí),會(huì)先執(zhí)行宏任務(wù)中的同步代碼。
2.進(jìn)行第一輪事件循環(huán)的時(shí)候會(huì)把全部的js腳本當(dāng)成一個(gè)宏任務(wù)來(lái)運(yùn)行。
3.如果執(zhí)行中遇到setTimeout之類(lèi)宏任務(wù),那么就把這個(gè)setTimeout內(nèi)部的函數(shù)推入「宏任務(wù)的隊(duì)列」中,下一輪宏任務(wù)執(zhí)行時(shí)調(diào)用。
4.如果執(zhí)行中遇到 promise.then() 之類(lèi)的微任務(wù),就會(huì)推入到「當(dāng)前宏任務(wù)的微任務(wù)隊(duì)列」中,在本輪宏任務(wù)的同步代碼都執(zhí)行完成后,依次執(zhí)行所有的微任務(wù)。
5.第一輪事件循環(huán)中當(dāng)執(zhí)行完全部的同步腳本以及微任務(wù)隊(duì)列中的事件,這一輪事件循環(huán)就結(jié)束了,開(kāi)始第二輪事件循環(huán)。
6.第二輪事件循環(huán)同理先執(zhí)行同步腳本,遇到其他宏任務(wù)代碼塊繼續(xù)追加到「宏任務(wù)的隊(duì)列」中,遇到微任務(wù),就會(huì)推入到「當(dāng)前宏任務(wù)的微任務(wù)隊(duì)列」中,在本輪宏任務(wù)的同步代碼執(zhí)行都完成后,依次執(zhí)行當(dāng)前所有的微任務(wù)。
7.開(kāi)始第三輪,循環(huán)往復(fù)...

下面用代碼來(lái)深入理解上面的機(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ù)注冊(cè)后分發(fā)到宏任務(wù)Event Queue。
3.接下來(lái)遇到了Promise,new Promise立即執(zhí)行,then函數(shù)分發(fā)到微任務(wù)Event Queue。
4.遇到console.log(),立即執(zhí)行。
5.整體代碼script作為第一個(gè)宏任務(wù)執(zhí)行結(jié)束。查看當(dāng)前有沒(méi)有可執(zhí)行的微任務(wù),執(zhí)行then的回調(diào)。
(第一輪事件循環(huán)結(jié)束了,我們開(kāi)始第二輪循環(huán)。)
6.從宏任務(wù)Event Queue開(kāi)始。我們發(fā)現(xiàn)了宏任務(wù)Event Queue中setTimeout對(duì)應(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作為第一個(gè)宏任務(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)在開(kāi)始執(zhí)行微任務(wù),我們發(fā)現(xiàn)了process1和then1兩個(gè)微任務(wù),執(zhí)行process1,輸出6。執(zhí)行then1,輸出8。
第一輪事件循環(huán)正式結(jié)束,這一輪的結(jié)果是輸出1,7,6,8。那么第二輪事件循環(huán)從setTimeout1宏任務(wù)開(kāi)始:
1.首先輸出2。接下來(lái)遇到了process.nextTick(),同樣將其分發(fā)到微任務(wù)Event Queue中,記為process2。
2.new Promise立即執(zhí)行輸出4,then也分發(fā)到微任務(wù)Event Queue中,記為then2。
3.現(xiàn)在開(kāi)始執(zhí)行微任務(wù),我們發(fā)現(xiàn)有process2和then2兩個(gè)微任務(wù)可以執(zhí)行輸出3,5。
第二輪事件循環(huán)結(jié)束,第二輪輸出2,4,3,5。第三輪事件循環(huán)從setTimeout2宏任務(wù)開(kāi)始:
1.直接輸出9,將process.nextTick()分發(fā)到微任務(wù)Event Queue中。記為process3。
2.直接執(zhí)行new Promise,輸出11。將then分發(fā)到微任務(wù)Event Queue中,記為then3。
3.執(zhí)行兩個(gè)微任務(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。
(請(qǐng)注意,node環(huán)境下的事件監(jiān)聽(tīng)依賴(lài)libuv與前端環(huán)境不完全相同,輸出順序可能會(huì)有誤差)

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.全部的代碼作為第一個(gè)宏任務(wù)進(jìn)入主線程執(zhí)行。
2.首先輸出1,是同步代碼。then回調(diào)作為微任務(wù)進(jìn)入到宏任務(wù)一的微任務(wù)隊(duì)列。
3.下面最外層的三個(gè)setTimeout分別是宏任務(wù)二、宏任務(wù)三、宏任務(wù)四按序排入宏任務(wù)隊(duì)列。
4.輸出2,現(xiàn)在宏任務(wù)一的同步代碼都執(zhí)行完成了接下來(lái)執(zhí)行宏任務(wù)一的微任務(wù)輸出3。
第一輪事件循環(huán)完成了
5.現(xiàn)在執(zhí)行宏任務(wù)二輸出4,后面的setTimeout作為宏任務(wù)五排入宏任務(wù)隊(duì)列。
第二輪事件循環(huán)完成了
6.執(zhí)行宏任務(wù)三輸出5,執(zhí)行宏任務(wù)四輸出6,宏任務(wù)四里面的setTimeout作為宏任務(wù)六。
7.執(zhí)行宏任務(wù)五輸出7,8。then回調(diào)作為宏任務(wù)五的微任務(wù)排入宏任務(wù)五的微任務(wù)隊(duì)列。
8.輸出同步代碼9,宏任務(wù)五的同步代碼執(zhí)行完了,現(xiàn)在執(zhí)行宏任務(wù)五的微任務(wù)。
9.輸出10,后面的setTimeout作為宏任務(wù)七排入宏任務(wù)的隊(duì)列。
宏任務(wù)五執(zhí)行完成了,當(dāng)前已經(jīng)是第五輪事件循環(huán)了。
10.執(zhí)行宏任務(wù)六輸出11,執(zhí)行宏任務(wù)七輸出12,

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

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

上面的案例只是用setTimeout和Promise模擬了一些場(chǎng)景來(lái)幫助理解,并沒(méi)有用到async/await下面我們從什么是async/await開(kāi)始講起。

async/await是什么?

我們創(chuàng)建了 promise 但不能同步等待它執(zhí)行完成。我們只能通過(guò) then 傳一個(gè)回調(diào)函數(shù)這樣很容易再次陷入 promise 的回調(diào)地獄。實(shí)際上,async/await 在底層轉(zhuǎn)換成了 promise 和 then 回調(diào)函數(shù)。也就是說(shuō),這是 promise 的語(yǔ)法糖。每次我們使用 await, 解釋器都創(chuàng)建一個(gè) promise 對(duì)象,然后把剩下的 async 函數(shù)中的操作放到 then 回調(diào)函數(shù)中。async/await 的實(shí)現(xiàn),離不開(kāi) Promise。從字面意思來(lái)理解,async 是“異步”的簡(jiǎn)寫(xiě),而 await 是 async wait 的簡(jiǎn)寫(xiě)可以認(rèn)為是等待異步方法執(zhí)行完成。

async/await用來(lái)干什么?

用來(lái)優(yōu)化 promise 的回調(diào)問(wèn)題,被稱(chēng)作是異步的終極解決方案。

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

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

await的等待機(jī)制?

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

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

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

async/await 在什么場(chǎng)景使用?

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

async/await如何使用?

假設(shè)一個(gè)業(yè)務(wù),分多個(gè)步驟完成,每個(gè)步驟都是異步的且依賴(lài)于上一個(gè)步驟的結(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 來(lái)實(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)部有語(yǔ)法錯(cuò)誤會(huì)觸發(fā)catch方法
})

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

我們來(lái)看在任務(wù)隊(duì)列中async/await的運(yùn)行機(jī)制,先給出大概方向再通過(guò)案例來(lái)證明:
1.async定義的是一個(gè)Promise函數(shù)和普通函數(shù)一樣只要不調(diào)用就不會(huì)進(jìn)入事件隊(duì)列。
2.async內(nèi)部如果沒(méi)有主動(dòng)return Promise,那么async會(huì)把函數(shù)的返回值用Promise包裝。
3.await關(guān)鍵字必須出現(xiàn)在async函數(shù)中,await后面不是必須要跟一個(gè)異步操作,也可以是一個(gè)普通表達(dá)式。
4.遇到await關(guān)鍵字,await右邊的語(yǔ)句會(huì)被立即執(zhí)行然后await下面的代碼進(jìn)入等待狀態(tài),等待await得到結(jié)果。
await后面如果不是 promise 對(duì)象, await會(huì)阻塞后面的代碼,先執(zhí)行async外面的同步代碼,同步代碼執(zhí)行完,再回到async內(nèi)部,把這個(gè)非promise的東西,作為 await表達(dá)式的結(jié)果。
await后面如果是 promise 對(duì)象,await 也會(huì)暫停async后面的代碼,先執(zhí)行async外面的同步代碼,等著 Promise 對(duì)象 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() 會(huì)先運(yùn)行async2(),5進(jìn)入等待狀態(tài)。
4.輸出3,這個(gè)時(shí)候先執(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后面雖然是一個(gè)直接量,但是還是會(huì)先執(zhí)行async函數(shù)外的同步代碼。
3.輸出3,進(jìn)入Promise輸出4,then回調(diào)進(jìn)入微任務(wù)隊(duì)列。
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ù)隊(duì)列。
2.執(zhí)行外面的同步代碼,輸出4,5。4后面的then進(jìn)入微任務(wù)隊(duì)列。
4.接下來(lái)執(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ù)隊(duì)列等待下一輪事件循環(huán)。
3.進(jìn)入async1()函數(shù)輸出2,await下面的代碼進(jìn)入等待狀態(tài)。
4.進(jìn)入async2()輸出3,then回調(diào)進(jìn)入微任務(wù)隊(duì)列。
5.現(xiàn)在執(zhí)行外面的同步代碼,輸出4,5,then回調(diào)進(jìn)入微任務(wù)隊(duì)列。
6.按序執(zhí)行微任務(wù),輸出6,7?,F(xiàn)在回到async1函數(shù)。
7.輸出data,也就是await關(guān)鍵字等到的內(nèi)容,接著輸出8。
8.進(jìn)行下一輪時(shí)間循環(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ù)隊(duì)列等待下一輪事件循環(huán)。
2.先執(zhí)行async1函數(shù),輸出1,6進(jìn)入等待狀態(tài),現(xiàn)在執(zhí)行async2。
3.輸出2,then回調(diào)進(jìn)入微任務(wù)隊(duì)列。
4.接下來(lái)執(zhí)行外面的同步代碼輸出3,then回調(diào)進(jìn)入微任務(wù)隊(duì)列。
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)多主要為了以后回顧,如果大家覺(jué)得我的理解有偏差歡迎指正。

緩緩先...

-^-

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

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

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

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