天天看點

面試官:說說對 Node.js 中的事件循環機制了解?

面試官:說說對 Node.js 中的事件循環機制了解?

一、是什麼

在浏覽器事件循環中,我們了解到

javascript

在浏覽器中的事件循環機制,其是根據

HTML5

定義的規範來實作

而在

NodeJS

中,事件循環是基于

libuv

實作,

libuv

是一個多平台的專注于異步IO的庫,如下圖最右側所示:

面試官:說說對 Node.js 中的事件循環機制了解?

上圖

EVENT_QUEUE

給人看起來隻有一個隊列,但

EventLoop

存在6個階段,每個階段都有對應的一個先進先出的回調隊列

二、流程

上節講到事件循環分成了六個階段,對應如下:

面試官:說說對 Node.js 中的事件循環機制了解?
  • timers階段:這個階段執行timer(setTimeout、setInterval)的回調
  • 定時器檢測階段(timers):本階段執行 timer 的回調,即 setTimeout、setInterval 裡面的回調函數
  • I/O事件回調階段(I/O callbacks):執行延遲到下一個循環疊代的 I/O 回調,即上一輪循環中未被執行的一些I/O回調
  • 閑置階段(idle, prepare):僅系統内部使用
  • 輪詢階段(poll):檢索新的 I/O 事件;執行與 I/O 相關的回調(幾乎所有情況下,除了關閉的回調函數,那些由計時器和 setImmediate() 排程的之外),其餘情況 node 将在适當的時候在此阻塞
  • 檢查階段(check):setImmediate() 回調函數在這裡執行
  • 關閉事件回調階段(close callback):一些關閉的回調函數,如:socket.on('close', ...)

每個階段對應一個隊列,當事件循環進入某個階段時, 将會在該階段内執行回調,直到隊列耗盡或者回調的最大數量已執行, 那麼将進入下一個處理階段

除了上述6個階段,還存在

process.nextTick

,其不屬于事件循環的任何一個階段,它屬于該階段與下階段之間的過渡, 即本階段執行結束, 進入下一個階段前, 所要執行的回調,類似插隊

流程圖如下所示:

面試官:說說對 Node.js 中的事件循環機制了解?

Node

中,同樣存在宏任務和微任務,與浏覽器中的事件循環相似

微任務對應有:

  • next tick queue:process.nextTick
  • other queue:Promise的then回調、queueMicrotask

宏任務對應有:

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

其執行順序為:

  • 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(function () {
    console.log('setTimeout0')
}, 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')
           

分析過程:

  • 先找到同步任務,輸出script start
  • 遇到第一個 setTimeout,将裡面的回調函數放到 timer 隊列中
  • 遇到第二個 setTimeout,300ms後将裡面的回調函數放到 timer 隊列中
  • 遇到第一個setImmediate,将裡面的回調函數放到 check 隊列中
  • 遇到第一個 nextTick,将其裡面的回調函數放到本輪同步任務執行完畢後執行
  • 執行 async1函數,輸出 async1 start
  • 執行 async2 函數,輸出 async2,async2 後面的輸出 async1 end進入微任務,等待下一輪的事件循環
  • 遇到第二個,将其裡面的回調函數放到本輪同步任務執行完畢後執行
  • 遇到 new Promise,執行裡面的立即執行函數,輸出 promise1、promise2
  • then裡面的回調函數進入微任務隊列
  • 遇到同步任務,輸出 script end
  • 執行下一輪回到函數,先依次輸出 nextTick 的函數,分别是 nextTick1、nextTick2
  • 然後執行微任務隊列,依次輸出 async1 end、promise3
  • 執行timer 隊列,依次輸出 setTimeout0
  • 接着執行 check  隊列,依次輸出 setImmediate
  • 300ms後,timer 隊列存在任務,執行輸出 setTimeout2

執行結果如下:

script start
async1 start
async2
promise1
promise2
script end
nextTick1
nextTick2
async1 end
promise3
setTimeout0
setImmediate
setTimeout2
           

最後有一道是關于

setTimeout

setImmediate

的輸出順序

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

setImmediate(() => {
  console.log("setImmediate");
});
           

輸出情況如下:

情況一:
setTimeout
setImmediate

情況二:
setImmediate
setTimeout
           

分析下流程:

  • 外層同步代碼一次性全部執行完,遇到異步API就塞到對應的階段
  • 遇到

    setTimeout

    ,雖然設定的是0毫秒觸發,但實際上會被強制改成1ms,時間到了然後塞入

    times

    階段
  • 遇到

    setImmediate

    塞入

    check

    階段
  • 同步代碼執行完畢,進入Event Loop
  • 先進入

    times

    階段,檢查目前時間過去了1毫秒沒有,如果過了1毫秒,滿足

    setTimeout

    條件,執行回調,如果沒過1毫秒,跳過
  • 跳過空的階段,進入check階段,執行

    setImmediate

    回調

這裡的關鍵在于這1ms,如果同步代碼執行時間較長,進入

Event Loop

的時候1毫秒已經過了,

setTimeout

先執行,如果1毫秒還沒到,就先執行了

setImmediate

參考文獻

  • https://segmentfault.com/a/1190000012258592
  • https://juejin.cn/post/6844904100195205133
  • https://vue3js.cn/interview/

--The End--

系列正在更新:8/14

點選下方卡片解鎖更多

面試官:說說對 Node.js 中的事件循環機制了解?

創作不易,星标、點贊、在看 三連支援

繼續閱讀