Nodejs中的事件循環(huán)機(jī)制

在瀏覽器事件循環(huán)中,我們了解到javascript在瀏覽器中的事件循環(huán)機(jī)制,其是根據(jù)html5定義的規(guī)范來實(shí)現(xiàn)的

而在nodejs中,事件循環(huán)是基于libuv實(shí)現(xiàn),libuv是一個多平臺的專注于異步IO的庫,如下圖

image.png

上圖EVENT_QUEUE給人看起來只有一個隊列,但是EventLoop存在6個階段,每個階段都有對應(yīng)的一個先進(jìn)先出的回調(diào)隊列

流程

image.png
  • timers階段:這個階段執(zhí)行timer(setTimeout,setInterval)的回調(diào)
  • 定時器檢測階段(timers):本階段執(zhí)行timer的回調(diào),即setTimeout,setInterval里面的回調(diào)函數(shù)
  • I/O 事件回調(diào)階段:執(zhí)行延遲到下一個循環(huán)迭代的I/O回調(diào),即上一輪循環(huán)中未被執(zhí)行的一些I/O 回調(diào)
  • 限制階段:僅供系統(tǒng)內(nèi)部使用
  • 輪訓(xùn)階段poll:檢索新的I/O事件,執(zhí)行與I/O相關(guān)的回調(diào),幾乎所有的情況下,除了關(guān)于的回調(diào)函數(shù),那些計時器和setImmediate()調(diào)度之外,其余情況node將在適當(dāng)?shù)臅r候在此阻塞
  • 檢查階段:setImmediate()回調(diào)函數(shù)在這里執(zhí)行
  • 關(guān)閉事件回調(diào)階段:一些關(guān)閉的回調(diào)函數(shù),如:socket.on('close',……)

每個階段對應(yīng)一個隊列,當(dāng)事件循環(huán)進(jìn)入某個階段時,將會在該階段內(nèi)執(zhí)行回調(diào),直到隊列好近或者回調(diào)的最大數(shù)量已執(zhí)行,那么將進(jìn)入下一個階段

除了上述6個階段,還存在process.nextTick,其部署事件循環(huán)的任何一個階段,屬于該階段與下階段之間的過度,即本階段執(zhí)行結(jié)束,進(jìn)入下一個階段前,所要執(zhí)行的回調(diào),類似插隊

image.png

在Nodejs中,同樣存在宏任務(wù)和微任務(wù),與瀏覽器中的時間循環(huán)相似

微任務(wù)對應(yīng)的有:

  • next tick queue:process.nextTick
  • other queue: Promise的then回調(diào),queueMicrotask

宏任務(wù)對應(yīng)的有:

  • timer queue:setTimeout,setInterval
  • poll queue: IO事件
  • check queue: setImmediate
  • close queue:close事件

其執(zhí)行順序為:

  • next tick microtask queue
  • other microtask queue
  • timer queue
  • poll queue
  • check queue
  • close queue
async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}

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

console.log("script start")

setTimeout(()=>{
    console.log("setTimout0")
},0)

setTimeout(function () {
    console.log('setTimeout2')
}, 300)

setImmediate(() => console.log('setImmediate'));

process.nextTick(() => console.log('nextTick1'));

async1();

process.nextTick(() => console.log('nextTick2'));

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

console.log('script end')

分析過程:

  • 先找到同步任務(wù),輸出script start
  • 遇到第一個setTimout將里面的函數(shù)放入timer隊列中
  • 遇到第二個setTimout,300ms后將里面的回調(diào)函數(shù)放到timer隊列中
  • 遇到第一個setImmediate,將里面的回調(diào)函數(shù)放到check隊列中
  • 遇到第一個nextTick,將里面的回調(diào)函數(shù)放到本輪同步任務(wù)執(zhí)行完畢后執(zhí)行
  • 執(zhí)行async1 函數(shù),輸出async1 start
  • 執(zhí)行async2 函數(shù),輸出async2 ,async2后面輸出async 1 end進(jìn)入微任務(wù),等待下一輪事件循環(huán)
  • 遇到第二個,將里面的回調(diào)函數(shù)放到本輪同步任務(wù)執(zhí)行完畢后執(zhí)行
  • 遇到new Promise,執(zhí)行里面的立即執(zhí)行函數(shù),出書promise1,promise2
  • then里面的回調(diào)函數(shù)進(jìn)入到微任務(wù)隊列
  • 遇到同步任務(wù),輸出script end
  • 執(zhí)行下一輪回調(diào)函數(shù),先依次輸出nextTick函數(shù),分別是nextTick1,nextTick2
  • 然后執(zhí)行微任務(wù)隊列,依次輸出async1 end,promise3
  • 執(zhí)行timer隊列,依次輸出setTimeout0
  • 接著執(zhí)行check隊列,依次輸出setImmedate
  • 300ms后,timer隊列存在任務(wù),執(zhí)行輸出setTimeout2
script start
async1 start
async2
promise1
promise2
script end
nextTick1
nextTick2
async1 end
promise3
setTimeout0
setImmediate
setTimeout2
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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