JavaScript執行機制-node事件迴圈

legendaryedu發表於2019-04-04

node環境下的事件迴圈機制

和瀏覽器有什麼不同?

在node中,事件迴圈表現出來的狀態和瀏覽器大致相同,但是node有一套自己的模型。

node事件迴圈依靠libuv引擎,node選擇chrome v8 作為js的直譯器,v8將js程式碼分析後,去掉用node相關的api,這些api最後由libuv引擎驅動,執行對應任務,把不同事件放在不同佇列等待主執行緒執行。

所以,實際上node中的事件迴圈存在於libuv引擎中。

事件迴圈模型

JavaScript執行機制-node事件迴圈

各個階段詳解

node中大致的事件迴圈順序

外部輸入資料---輪詢階段(poll)---檢查階段(check)---關閉事件回撥階段(close callback)---定時器檢查階段(timer)---I/O事件回撥階段(I/O callback)---閒置階段(idle,prepare)---輪詢階段...

  • timers:執行定時器佇列回撥 setTimeout() setInterval()
  • I/O callbacks:執行幾乎所有回撥,不包括 close 事件,定時器,和setImmediate()回撥
  • idle,prepare:僅在內部使用
  • poll:等待新的I/O事件,node在一些特殊情況下會阻塞在這裡
  • check:setImmediate()回撥在這個階段執行
  • close callbacks:如 socket.on('close',....)

poll 階段

當v8引擎將js程式碼解析後傳入libuv引擎,首先計入poll階段。

poll階段執行邏輯:

先檢視poll queue 是否有事件,有任務,按照先進先出依次執行回撥。

當queue為空,會檢查是否有 setImmediate()的callback,如有,進入check階段的callback。同時也會檢查是否有到期的timer,如果有,就把到期的timer的callback按照呼叫順序,放入到 timer queue 中,之後迴圈進入 timer 階段的queue 中的callback。

這兩者順序是不固定的,受執行環境影響。如果兩者的queue都是空的,loop就在poll階段停留,直到有一個I/O事件返回。

poll階段執行poll queue中的回撥實際上不會無限執行過下去。當 1 所有回撥執行完畢 2 執行數超過了node限制,poll階段會終止執行 poll queue中的下一個回撥。

check 階段

專門用來執行 setImmediate()方法的回撥,當poll階段進入空閒,並且 setImmediate()裡面有callback,事件迴圈進入這個階段。

close 階段

當一個socket連線或者一個handle關閉(socket.destory())close事件會在這個階段執行回撥。否則事件會用 process.nextTick()方法傳送出去。

timer 階段

這個階段以先進先出的方式執行所有到期的timer加入到timer佇列裡面的callback,一個timer callback 指的是 通過 setTimeout或者 setInterval 函式設定的回撥函式。

I/O callback 階段

這個階段主要執行大部分I/O事件的回撥,包括一些作業系統執行的回撥。

如:一個tcp連線出錯了,系統執行回撥捕獲錯誤報告

process.nextTick(),setTimeout(),setImmediate()區別和使用場景

process.nextTick()

node中存在一種特殊的佇列,nextTick queue.

這個佇列回撥執行雖然沒有被表示為一個階段,但是這些時間會在每一個階段完畢準備進入下一個階段時優先執行。 當事件迴圈進入下一個階段之前,會先檢查 nextTick queue是否有任務,如果有,會先清空這個佇列。不過需要注意這個操作在佇列清空前是不會停止的,所以,使用不當,會導致死迴圈,直至記憶體洩漏。

setTimeout(),setImmediate()

setTimeout()是定義一個回撥,希望在一定時間後,第一時間去執行。but,受到各種影響,該回撥並不會在時間間隔後,精準執行。node會在可以執行timer回撥的第一時間去執行你所設定的回撥任務。

setImmediate(),字面上看,是立即執行。實際上,他會在一個固定的階段才會執行回撥,即poll階段之後。

誰會先執行????

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

setImmediate(() => {
    console.log('immediate');
});
複製程式碼

答案是不一定,這取決於程式碼執行環境。執行環境可能導致同步佇列裡面兩個方法順序隨機決定。

但是,在I/O事件的回撥中,下面程式碼順序是始終不會變的。

const fs = require('fs');

fs.readFile(__filename, () => {
    setTimeout(() => {
        console.log('timeout');
    }, 0);
    setImmediate(() => {
        console.log('immediate');
    });
});
答案永遠是不變的
immediate
timeout
複製程式碼

因為在I/O事件的回撥中,setImmediate方法的回撥永遠在timer的回撥前執行。

相關文章