Node 中的 Event Loop 和瀏覽器中的是完全不相同的東西。
Node 的 Event Loop 分為 6 個(gè)階段,它們會(huì)按照順序反復(fù)運(yùn)行。每當(dāng)進(jìn)入某一個(gè)階段的時(shí)候,都會(huì)從對(duì)應(yīng)的回調(diào)隊(duì)列中取出函數(shù)去執(zhí)行。當(dāng)隊(duì)列為空或者執(zhí)行的回調(diào)函數(shù)數(shù)量到達(dá)系統(tǒng)設(shè)定的閾值,就會(huì)進(jìn)入下一階段。
(1)Timers(計(jì)時(shí)器階段):初次進(jìn)入事件循環(huán),會(huì)從計(jì)時(shí)器階段開(kāi)始。此階段會(huì)判斷是否存在過(guò)期的計(jì)時(shí)器回調(diào)(包含 setTimeout 和 setInterval),如果存在則會(huì)執(zhí)行所有過(guò)期的計(jì)時(shí)器回調(diào),執(zhí)行完畢后,如果回調(diào)中觸發(fā)了相應(yīng)的微任務(wù),會(huì)接著執(zhí)行所有微任務(wù),執(zhí)行完微任務(wù)后再進(jìn)入 Pending callbacks 階段。
(2)Pending callbacks:執(zhí)行推遲到下一個(gè)循環(huán)迭代的I / O回調(diào)(系統(tǒng)調(diào)用相關(guān)的回調(diào))。
(3)Idle/Prepare:僅供內(nèi)部使用。
(4)Poll(輪詢階段):
當(dāng)回調(diào)隊(duì)列不為空時(shí):會(huì)執(zhí)行回調(diào),若回調(diào)中觸發(fā)了相應(yīng)的微任務(wù),這里的微任務(wù)執(zhí)行時(shí)機(jī)和其他地方有所不同,不會(huì)等到所有回調(diào)執(zhí)行完畢后才執(zhí)行,而是針對(duì)每一個(gè)回調(diào)執(zhí)行完畢后,就執(zhí)行相應(yīng)微任務(wù)。執(zhí)行完所有的回調(diào)后,變?yōu)橄旅娴那闆r。
當(dāng)回調(diào)隊(duì)列為空時(shí)(沒(méi)有回調(diào)或所有回調(diào)執(zhí)行完畢):但如果存在有計(jì)時(shí)器(setTimeout、setInterval和setImmediate)沒(méi)有執(zhí)行,會(huì)結(jié)束輪詢階段,進(jìn)入 Check 階段。否則會(huì)阻塞并等待任何正在執(zhí)行的I/O操作完成,并馬上執(zhí)行相應(yīng)的回調(diào),直到所有回調(diào)執(zhí)行完畢。
(5)Check(查詢階段):會(huì)檢查是否存在 setImmediate 相關(guān)的回調(diào),如果存在則執(zhí)行所有回調(diào),執(zhí)行完畢后,如果回調(diào)中觸發(fā)了相應(yīng)的微任務(wù),會(huì)接著執(zhí)行所有微任務(wù),執(zhí)行完微任務(wù)后再進(jìn)入 Close callbacks 階段。
(6)Close callbacks:執(zhí)行一些關(guān)閉回調(diào),比如socket.on('close', ...)等。
下面來(lái)看一個(gè)例子,首先在有些情況下,定時(shí)器的執(zhí)行順序其實(shí)是隨機(jī)的
對(duì)于以上代碼來(lái)說(shuō),setTimeout 可能執(zhí)行在前,也可能執(zhí)行在后首先 setTimeout(fn, 0) === setTimeout(fn, 1),這是由源碼決定的進(jìn)入事件循環(huán)也是需要成本的,如果在準(zhǔn)備時(shí)候花費(fèi)了大于 1ms 的時(shí)間,那么在 timer 階段就會(huì)直接執(zhí)行 setTimeout 回調(diào)那么如果準(zhǔn)備時(shí)間花費(fèi)小于 1ms,那么就是 setImmediate 回調(diào)先執(zhí)行了當(dāng)然在某些情況下,他們的執(zhí)行順序一定是固定的,比如以下代碼:
在上述代碼中,setImmediate 永遠(yuǎn)先執(zhí)行。因?yàn)閮蓚€(gè)代碼寫(xiě)在 IO 回調(diào)中,IO 回調(diào)是在 poll 階段執(zhí)行,當(dāng)回調(diào)執(zhí)行完畢后隊(duì)列為空,發(fā)現(xiàn)存在 setImmediate 回調(diào),所以就直接跳轉(zhuǎn)到 check 階段去執(zhí)行回調(diào)了。
上面都是 macrotask 的執(zhí)行情況,對(duì)于 microtask 來(lái)說(shuō),它會(huì)在以上每個(gè)階段完成前清空 microtask 隊(duì)列,下圖中的 Tick 就代表了 microtask
對(duì)于以上代碼來(lái)說(shuō),其實(shí)和瀏覽器中的輸出是一樣的,microtask 永遠(yuǎn)執(zhí)行在 macrotask 前面。最后來(lái)看 Node 中的 process.nextTick,這個(gè)函數(shù)其實(shí)是獨(dú)立于 Event Loop 之外的,它有一個(gè)自己的隊(duì)列,當(dāng)每個(gè)階段完成后,如果存在 nextTick 隊(duì)列,就會(huì)清空隊(duì)列中的所有回調(diào)函數(shù),并且優(yōu)先于其他 microtask 執(zhí)行。
對(duì)于以上代碼,永遠(yuǎn)都是先把 nextTick 全部打印出來(lái)。