Node.js 的事件循環流程大體以下:javascript
┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘
每一個階段都有本身的任務隊列,當本階段的任務隊列都執行完畢,或者達到了執行的最大任務數,就會進入到下一個階段。java
這個階段會執行被 setTimeout
和 setInterval
設置的定時任務。
固然,這個定時並非準確的,而是在超過了定時時間後,一旦獲得執行機會,就馬上執行。node
這個階段會執行一些和底層系統有關的操做,例如TCP鏈接返回的錯誤等。這些錯誤發生時,會被Node 推遲到下一個循環中執行。git
這個階段是用來執行和 IO 操做有關的回調的,Node會向操做系統詢問是否有新的 IO 事件已經觸發,而後會執行響應的事件回調。幾乎全部除了 定時器事件、 setImmediate()
和 close callbacks
以外操做都會在這個階段執行。github
這個階段會執行 setImmediate()
設置的任務。網絡
若是一個 socket
或 handle(句柄)
忽然被關閉了,例如經過 socket.destroy()
關閉了,close
事件將會在這個階段發出。socket
事件循環初始化以後,會按照上圖所示的流程進行:oop
pending callback
回調;idle
、 prepare
階段,這裏會執行 Node 內部的一些邏輯;poll
輪詢階段。在這個階段會執行全部的 IO 回調,如 讀取文件,網絡操做等。 poll
階段有一個 poll queue
任務隊列。這個階段的執行過程相對較長,具體以下:timeout
定時隊列是否有可執行的任務,若是有,會跳轉到 定時器階段
執行。定時器任務
,就會檢查 poll queue
任務隊列,若是不爲空,會遍歷執行全部任務直到都執行完畢或者達到能執行的最大的任務數量。poll queue
任務隊列執行完成後,會檢查 setImmediate
任務隊列是否有任務,若是有的話,事件循環會轉移到下一個 check
階段。setImmediate
任務,那麼,Node 將會在此等待,等待新的 IO 回調的到來,並馬上執行他們。注意 :這個等待不會一直等待下去,而是達到一個限定條件以後,繼續轉到下一個階段去執行。ui
setTimeout()
和 setImmediate()
其實也不算祕密,只是我是在剛剛查閱資料才知道的。
那就是:在 Node 中,setTimeout(callback, 0)
會被轉換爲 setTimeout(callback, 1)
。
詳情請參考 這裏 。操作系統
setTimeout()
和 setImmediate()
的執行順序下面這兩個定時任務執行的順序在不一樣狀況下,表現不一致。
setTimeout(function() { console.log('timeout'); }, 0); setImmediate(function() { console.log('immediate'); });
若是在普通的代碼執行階段(例如在最外層代碼塊中),設置這兩個定時任務,他們的執行順序是不固定的。
setTimeout(callback, 0)
已經被轉換成爲 setTimeout(callback, 1)
,因此進入 定時器
階段時,會根據當前時間判判定時是否超過了 1ms
。1ms
,immediate
任務會先執行;若是存在延遲,而且這個時間達到了 1ms
的界限, timeout
任務就會首先執行。若是咱們在 IO 回調中設置了這兩個定時器,那麼 setImmediate
任務會首先執行,緣由以下:
poll phase
輪詢階段以前會先檢查是否有 timer
定時任務。timer
定時任務,纔會執行後面的 IO 回調。setTimeout
定時任務,這時已通過了 timer
檢查階段,因此 timer
定時任務會被推遲到下一個循環中執行。process.nextTick()
不管在事件循環的哪一個階段,只要使用 process.nextTick()
添加了回調任務,Node 都會在進入下一階段以前把 nextTickQueue
隊列中的任務執行完。
setTimeout(function() { setImmediate(() => { console.log('immediate'); }); process.nextTick(() => { console.log('nextTick'); }); }, 0); // nextTick // immediate
上述代碼中,老是先執行 nextTick
任務,就是由於在循環在進入下一個階段以前會先執行 nextTickQueue
中的任務。下面代碼的執行結果也符合預期。
setImmediate(() => { setTimeout(() => { console.log('timeout'); }, 0); process.nextTick(() => { console.log('nextTick'); }); }); // nextTick // timeout