node中的Event Loop

關於事件這一塊在《深刻淺出的nodejs》中不多講到,書裏面只是在第三章說起了4個API方法,好比兩個定時器(setTimeout和setInterval),process.nextTick()和setImmediate。node

瀏覽器中的event loopgit

前篇回顧

setTimeout(()=>{
    console.log('timer1')

    Promise.resolve().then(function() {
        console.log('promise1')
    })
}, 0)

setTimeout(()=>{
    console.log('timer2')

    Promise.resolve().then(function() {
        console.log('promise2')
    })
    setTimeout(() => {
    	console.log('timer3')
    }, 0)
}, 0)

Promise.resolve().then(function() {
    console.log('promise3')
})

console.log('start')
複製代碼

瀏覽器中輸出結果:github

start
promise3
timer1
promise1
timer2
promise2
timer3
複製代碼

這個輸出結果的緣由咱們已經在上一篇文章中說明,本章就很少加贅述。c#

在nodejs中,運行卻能獲得不一樣的結果,讓咱們先來過一下node的事件模型。api

start
promise3
timer1
timer2
promise1
promise2
timer3
複製代碼

node中的事件循環模型

node的事件循環分爲6個階段promise

源代碼地址有興趣的能夠去看一下源代碼瀏覽器

node-phase.png

六個階段的功能以下:異步

  • timers:這個階段執行定時器隊列中的回調,好比setTimeout()和setInterval()
  • I/O callbacks:這個階段執行幾乎全部的回調。可是不包括close事件,定時器和setImmediate的回調。
  • idle,prepare:僅在內部使用。
  • poll:等待新的I/O事件,node會在一些特殊的狀況下使用
  • check:setImmediate()的回調會在這個中執行。
  • close callbacks:例如socket.on('close', ...)執行close的回調。

poll階段

當有數據或者鏈接傳入事件循環的時候,先進入的是poll階段,這個階段,先檢查poll queue中是否有事件,有任務就按先進先出的順序執行回調,若是隊列爲空,那麼會先檢查是否有到期的setImmdiate,若是有,將其的callback推入check隊列中,同時還會檢查是否有到期的timer,若是有,將其callback推入到timers隊列中。若是前面二者都爲空,那麼直接進入I/O callback,並執行這個事件的callback。socket

check階段

check階段專門用來執行setImmidate的回調函數。async

close階段

用於執行close事件的回調函數

timer階段

用於執行定時器設置的回調函數

I/O callback階段

用於執行大部分I/O事件的回調函數。

process.nextTick

這個鉤子在node的事件循環模型中沒有說起,可是node中有一個特殊的隊列,nextTick queue。在node事件循環進入到下一個階段的時候,都會去檢測nextTick queue中有沒有清空,若是沒有清空,那麼就會去清空nextTick queue中的事件。

循環過程

官網的文檔裏面有那麼一段話:

When Node.js starts, it initializes the event loop, processes the provided input script (or drops into the REPL, which is not covered in this document) which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop.

  • 循環開始以前

一、全部同步任務 二、腳本任務中發送的api請求 三、規劃定時器同步任務的生效時間 四、執行process.nextTick()

  • 開始循環

第一種狀況

一、清空當前循環內的 Timers Queue,清空NextTick Queue,清空Microtask Queue 二、清空當前循環內的 I/O Queue,清空NextTick Queue,清空Microtask Queue 三、進入poll階段 四、清空當前循環內的 Check Queue,清空NextTick Queue,清空Microtask Queue 五、清空當前循環內的 Close Queue,清空NextTick Queue,清空Microtask Queue

第二種狀況

一、先進入poll階段 二、清空當前循環內的 Check Queue,清空NextTick Queue,清空Microtask Queue 三、清空當前循環內的 Close Queue,清空NextTick Queue,清空Microtask Queue 四、清空當前循環內的 Timers Queue,清空NextTick Queue,清空Microtask Queue 五、清空當前循環內的 I/O Queue,清空NextTick Queue,清空Microtask Queue

  • setTimeout 和 setImmediate 的區別
setTimeout(() => {
  console.log('timeout')
}, 0);

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

直接運行腳本,輸出的結果是

timeout
immediate
複製代碼

當咱們把他放在同一個I/O循環中運行

const fs = require('fs');

fs.readFile(__filename, () => {
    setTimeout(() => {
        console.log('timeout')
    }, 0)
    setImmediate(() => {
        console.log('immediate')
    })
})
複製代碼

輸出的結果是

immediate
timeout
複製代碼
  • process.nextTick和microtask
process.nextTick(() => {
    console.log('nextTick')
})

Promise.resolve().then(() => {
    console.log('promise')
})
複製代碼

輸出的結果是

nextTick
promise
複製代碼

nodejs中的實現方式:microtask queue的任務經過runMicrotasks將microtask queue中的task放入到nextTick中,因此microtask的任務會在nextTick queue以後執行。

  • process.nextTick() 和 setImmediate()

書本中是推薦使用setImmediate(),用戶若是遞歸調用process.nextTick()的時候,會形成I/O被榨乾。而使用setImmediate,只會在check中執行,不至於異步調用的時候沒法執行。

相關文章
相關標籤/搜索