在node中,事件循環表現出來的狀態和瀏覽器大體相同,可是node有一套本身的模型。javascript
node事件循環依靠libuv引擎,node選擇chrome v8 做爲js的解釋器,v8將js代碼分析後,去掉用node相關的api,這些api最後由libuv引擎驅動,執行對應任務,把不一樣事件放在不一樣隊列等待主線程執行。java
因此,實際上node中的事件循環存在於libuv引擎中。node
node中大體的事件循環順序chrome
外部輸入數據---輪詢階段(poll)---檢查階段(check)---關閉事件回調階段(close callback)---定時器檢查階段(timer)---I/O事件回調階段(I/O callback)---閒置階段(idle,prepare)---輪詢階段...api
當v8引擎將js代碼解析後傳入libuv引擎,首先計入poll階段。瀏覽器
poll階段執行邏輯:socket
先查看poll queue 是否有事件,有任務,按照先進先出依次執行回調。tcp
當queue爲空,會檢查是否有 setImmediate()的callback,若有,進入check階段的callback。同時也會檢查是否有到期的timer,若是有,就把到期的timer的callback按照調用順序,放入到 timer queue 中,以後循環進入 timer 階段的queue 中的callback。函數
這二者順序是不固定的,受運行環境影響。若是二者的queue都是空的,loop就在poll階段停留,直到有一個I/O事件返回。oop
poll階段執行poll queue中的回調實際上不會無限執行過下去。當 1 全部回調執行完畢 2 執行數超過了node限制,poll階段會終止執行 poll queue中的下一個回調。
專門用來執行 setImmediate()方法的回調,當poll階段進入空閒,而且 setImmediate()裏面有callback,事件循環進入這個階段。
當一個socket鏈接或者一個handle關閉(socket.destory())close事件會在這個階段執行回調。不然事件會用 process.nextTick()方法發送出去。
這個階段以先進先出的方式執行全部到期的timer加入到timer隊列裏面的callback,一個timer callback 指的是 經過 setTimeout或者 setInterval 函數設置的回調函數。
這個階段主要執行大部分I/O事件的回調,包括一些操做系統執行的回調。
如:一個tcp鏈接出錯了,系統執行回調捕獲錯誤報告
node中存在一種特殊的隊列,nextTick queue.
這個隊列回調執行雖然沒有被表示爲一個階段,可是這些時間會在每個階段完畢準備進入下一個階段時優先執行。 當事件循環進入下一個階段以前,會先檢查 nextTick queue是否有任務,若是有,會先清空這個隊列。不過須要注意這個操做在隊列清空前是不會中止的,因此,使用不當,會致使死循環,直至內存泄漏。
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的回調前執行。