node環境下的事件迴圈機制
和瀏覽器有什麼不同?
在node中,事件迴圈表現出來的狀態和瀏覽器大致相同,但是node有一套自己的模型。
node事件迴圈依靠libuv引擎,node選擇chrome v8 作為js的直譯器,v8將js程式碼分析後,去掉用node相關的api,這些api最後由libuv引擎驅動,執行對應任務,把不同事件放在不同佇列等待主執行緒執行。
所以,實際上node中的事件迴圈存在於libuv引擎中。
事件迴圈模型
各個階段詳解
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的回撥前執行。