Node中的事件環
- 我們寫的 js 代碼會交給 v8 引擎進⾏處理
- 代碼中可能會調⽤ nodeApi,node 會交給 libuv 庫處理
- libuv 通過阻塞 i/o 和多線程實作了異步 io
- 通過事件驅動的⽅式,将結果放到事件隊列中,最終交給我們的應⽤。
本階段執⾏已經被 setTimeout() 和 setInterval()的排程回調函數。
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
| 執⾏延遲到下⼀個循環疊代的 I/O 回調。
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
| 僅系統内部使⽤。
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘
| 檢索新的I/O事件;執⾏與 I/O相關的回調 ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
| setImmediate() 回調函數在這⾥執⾏。 └───────────────┘
│ ┌─────────────┴─────────────┐
│ │ check │
│ └─────────────┬─────────────┘
| ⼀些關閉的回調函數
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
這⾥每⼀個階段都對應⼀個事件隊列,當 event loop 執⾏到某個階段時會将目前階段對應的隊列依次執⾏。當該隊列已⽤盡或達到回調限制,事件循環将移動到下⼀階段。
process.nextTick() 從技術上講不是事件循環的⼀部分。優先級⾼于微任務
浏覽器:每執行完畢一個宏任務,會清空微任務 從 node10+ 後執行機制和我們的浏覽器一樣 新的
以前的 node 事件環是每個階段的宏任務都被清空了,才會執行微任務 老的
- 宏任務
- timers 隊列 用來放定時器
- poll 輪訓(處理 i/o 的回調)
- check 處理 setImmediate
- 微任務:process.nextTick > promise.then
Promise.resolve().then(()=>{
console.log('promise')
})
process.nextTick(()=>{ // 這個方法用的多一些
console.log('nextTick')
})
執行流程
主棧代碼執行完畢-》會檢測 timer 中有沒有回調,全部清空後進入到下一個階段 -》poll(有 i/o 的回調繼續處理)-> 看有沒有 check,如果有就清空
- 預設如果沒有 check 也沒有 timer,代碼邏輯還有沒執行完的回調,此時這個線程會在 poll 中等待
- 如果沒有 timer -》 poll(有 i/o 的回調繼續處理)-》 沒有 check 了
poll 階段:
- 檢測 Poll 隊列中是否為空,如果不為空則執⾏隊列中的任務,直到逾時或者全部執⾏完畢。
- 執⾏完畢後檢測 setImmediate 隊列是否為空,如果不為空則執⾏ check 階段,如果為空則等待時間到達。時間到達後回到 timer 階段
- 等待時間到達時可能會出現新的 callback,此時也在目前階段被清空
setTimeout(() => { // 執⾏順序不确定
console.log('settimeout')
})
setImmediate(() => {
console.log('check')
})
const fs = require('fs');
fs.readFile('./package.json', () => { // poll階段的下⼀個階段是check
setTimeout(() => { // timer
console.log('settimeout')
})
setImmediate(() => { // check
console.log('check')
})
});