Node.js中的事件循環,Timers和process.nextTick() 的探索之路

事件循環

事件循環就是node.js去作一些非阻塞I/O操做,那麼問題來了,非阻塞操做又是什麼呢?有一個事實對於js開發者都熟知的是,js是單線程的,也就是說在一段時間內只可以處理一種任務,其餘任務要執行須要等待當前任務執行完以後再開始。node

因爲大部分的現代內核都是多線程的,它們可以處理不一樣的操做執行。當其中的一個操做完成時,內核會告訴node,回調函數將會被加入到執行隊列中等待被執行。api

事件循環有好幾個階段,每一個階段都有先進先出的回調隊列被執行。每一個階段都有它的特別之處,當事件循環進入被給予的階段時,它將會執行確切的操做,直到上一隊列已經執行完接着執行在該階段的回調,當該階段上的全部隊列或回調都執行完成以後,事件循環會轉向下一階段多線程

階段預覽

  1. times:這個階段執行把setTimeout()和setInterval()列入計劃的回調
  2. pending callbacks:執行I/O回調
  3. idle, prepare: 僅爲內部使用
  4. poll: 獲取新的I/O事件;執行I/O相關的回調(大部分關閉回調的異常)
  5. check: setImmediate()回調將會被喚起
  6. close callbacks: 一些關閉的回調,例如socket.on('close', ...)

times

一個timer指定了一個閾值,在一個回調被執行以後而不是你想要讓它執行的肯定的時間。timer的回調在給定的肯定時間以後將會盡量早的執行,然而,操做系統或者其它回調的執行可能會延遲timer的回調異步

pending callbacks

這個階段執行一些系統操做的回調,例如tcp錯誤的類型socket

poll

這個poll階段有兩個主要的函數:tcp

  1. 計算它應該阻塞的時間
  2. 在該隊列中處理事件

當事件循環進入poll階段,而且沒有timers被安排執行,如下之一將會發生:ide

  1. 若是poll隊列不爲空,事件循環將會同步迭代執行它的調用隊列直到隊列已經被執行完成
  2. 若是poll隊列是空的,如下之一將會發生: 若是腳本中有setImmediate,事件循環將會中止poll階段,繼續轉向check階段去執行這些被安排的腳本 若腳本中沒有setImmediate,事件循環將會等待被添加進隊列的回調,並立刻執行它們

一旦poll隊列是空的,事件循環將會檢查那些已經到達了設置的閾值的timers。若一個或更多的timers已經準備好,事件循環將會轉向執行timers階段,從而去執行這些timers的回調函數函數

check

這個階段在poll階段已經完成以後容許人立刻執行回調,若是poll階段變爲了閒置狀態,且腳本中有setImmediate,事件循環將會轉向check階段而不是等待。 setImmediate其實是一個肯定的timer,它運行在一個事件循環的獨立的階段。它使用了一個libuv api,是在poll階段完成以後,執行這些安排的回調oop

close callbacks

若是一個socket或者是處理忽然的被關閉了,這個close事件將會在這個階段觸發。另外,它將會經過 process.nextTick觸發。ui

setImmediate() VS setTimeout()

他們很相似,可是表現卻不一樣,這取決於他們什麼時候被調用

  1. setImmediate()被設計爲一旦當前的poll階段完成時,執行回調
  2. setTimeout()是要在設置一個最小閾值的時間以後執行回調

他們在被調用的不一樣環境下執行順序會有所不一樣,然而,若在I/O循環以內,immediate的回調老是先執行

const fs = require('fs');

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

process.nextTick()

儘管是異步api的一部分,但不是事件循環的部分。這個nextTickQueue將會在當前操做完成以後被處理,無論當前事件循環處在哪一階段

setImmediate() VS process.nextTick()

  1. 在同一階段process.nextTick()將會當即觸發執行
  2. setImmediate()在下一次迭代循環中觸發

爲何使用process.nextTick()

  1. 容許用戶處理錯誤,在事件循環繼續以前清除不須要的資源或者是再次發出請求
  2. 有時候須要在執行棧結束時但要在事件循環繼續以前執行該回調函數

舉個栗子,若是要在函數構造器中觸發一個事件,若按照如下寫法,是不會被調用執行的,由於該代碼並無被處理,構造函數尚未被執行完成。

const EventEmitter = require('events');
const util = require('util');

function MyEmitter() {
  EventEmitter.call(this);
  this.emit('event'); // 該事件觸發無效
}
util.inherits(MyEmitter, EventEmitter);

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('an event occurred!');
});
複製代碼

若使用了process.nextTick(),就能夠在構造器執行完成以後觸發該事件,從而觸發回調

const EventEmitter = require('events');
const util = require('util');

function MyEmitter() {
  EventEmitter.call(this);

  // use nextTick to emit the event once a handler is assigned
  process.nextTick(() => {
this.emit('event');
  });
}
util.inherits(MyEmitter, EventEmitter);

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('an event occurred!');
});
複製代碼

參考資料:nodejs.org/en/docs/gui…

相關文章
相關標籤/搜索