NodeJS的Event Loop是用Libuv實現(xiàn)的。核心驅(qū)動為uv_run函數(shù),使用的是UV_RUN_ONCE模式,盡可能在一次uv_run周期中處理I/O的回調(diào)。(loop alive會判斷有沒有進(jìn)行中的異步請求。)

NodeJS的一次Event Loop周期會經(jīng)歷N個階段,每個階段結(jié)束后會進(jìn)入下個階段。來自官方NodeJS文檔。
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
timer:處理就緒的timer 回調(diào)隊列。(setTimeout或setInterval的callback)
I/O:處理就緒的I/O 回調(diào)隊列。(對應(yīng)uv_run的pending階段)
idle,prepare:處理就緒的internal 回調(diào)隊列。
poll:處理就緒的I/O 回調(diào)隊列或阻塞。
check:處理就緒的check 回調(diào)隊列。(setImmediate的callback)
close:處理就緒的句柄關(guān)閉回調(diào)。(資源回收時設(shè)定的close 回調(diào))
當(dāng)一個階段將當(dāng)前屬于自己的回調(diào)隊列處理完畢就會進(jìn)入下一個階段。所謂的“就緒”,是指進(jìn)入這個【階段前】就已經(jīng)完成異步請求準(zhǔn)備觸發(fā)的回調(diào)。
poll階段比較特殊,他有可能阻塞Node的線程。
如果在當(dāng)前Event Loop周期有過I/O回調(diào)事件的產(chǎn)生,又或是其他階段有異步回調(diào)可以處理,那么poll階段就不會阻塞。
如果沒有,Node的線程就會在這階段休眠,等待I/O或timer回調(diào)事件的產(chǎn)生。
poll階段的阻塞作用在于沒有可執(zhí)行的異步事件時,就此休眠線程,避免忙輪詢。(查資料很容易得知,poll階段內(nèi)核是選擇各個平臺最好的實現(xiàn)。然而,似乎為了統(tǒng)一在各個平臺的Event Loop,這個階段只會被I/O回調(diào)事件喚醒或超時結(jié)束。)
Event Loop在選擇低耗能的阻塞線程前,會盡可能地完成一次Loop周期,這樣的行為正好說明了它非常注重每種異步回調(diào)的實時性。至于為什么要劃分如此多的隊列和階段就不得而知了。
實際上,在一次Event Loop的close階段后,還會進(jìn)行一次timer回調(diào)隊列的處理。因為有poll階段的阻塞耗時,這時很可能有超時的回調(diào)可以執(zhí)行。
值得一提的是,process.nextTick和Promise事件會往microTask隊列加入回調(diào),而microTask會在每個階段結(jié)束時清空,這時Event Loop才進(jìn)入下一個階段。