事件循環就是node.js去作一些非阻塞I/O操做,那麼問題來了,非阻塞操做又是什麼呢?有一個事實對於js開發者都熟知的是,js是單線程的,也就是說在一段時間內只可以處理一種任務,其餘任務要執行須要等待當前任務執行完以後再開始。node
因爲大部分的現代內核都是多線程的,它們可以處理不一樣的操做執行。當其中的一個操做完成時,內核會告訴node,回調函數將會被加入到執行隊列中等待被執行。api
事件循環有好幾個階段,每一個階段都有先進先出的回調隊列被執行。每一個階段都有它的特別之處,當事件循環進入被給予的階段時,它將會執行確切的操做,直到上一隊列已經執行完接着執行在該階段的回調,當該階段上的全部隊列或回調都執行完成以後,事件循環會轉向下一階段多線程
一個timer指定了一個閾值,在一個回調被執行以後而不是你想要讓它執行的肯定的時間。timer的回調在給定的肯定時間以後將會盡量早的執行,然而,操做系統或者其它回調的執行可能會延遲timer的回調異步
這個階段執行一些系統操做的回調,例如tcp錯誤的類型socket
這個poll階段有兩個主要的函數:tcp
當事件循環進入poll階段,而且沒有timers被安排執行,如下之一將會發生:ide
一旦poll隊列是空的,事件循環將會檢查那些已經到達了設置的閾值的timers。若一個或更多的timers已經準備好,事件循環將會轉向執行timers階段,從而去執行這些timers的回調函數函數
這個階段在poll階段已經完成以後容許人立刻執行回調,若是poll階段變爲了閒置狀態,且腳本中有setImmediate,事件循環將會轉向check階段而不是等待。 setImmediate其實是一個肯定的timer,它運行在一個事件循環的獨立的階段。它使用了一個libuv api,是在poll階段完成以後,執行這些安排的回調oop
若是一個socket或者是處理忽然的被關閉了,這個close事件將會在這個階段觸發。另外,它將會經過 process.nextTick觸發。ui
他們很相似,可是表現卻不一樣,這取決於他們什麼時候被調用
他們在被調用的不一樣環境下執行順序會有所不一樣,然而,若在I/O循環以內,immediate的回調老是先執行
const fs = require('fs');
fs.readFile(__filename, () ={
setImmediate(() ={
console.log('immediate');
});
setTimeout(() ={
console.log('timeout');
}, 0);
});
複製代碼
儘管是異步api的一部分,但不是事件循環的部分。這個nextTickQueue將會在當前操做完成以後被處理,無論當前事件循環處在哪一階段
舉個栗子,若是要在函數構造器中觸發一個事件,若按照如下寫法,是不會被調用執行的,由於該代碼並無被處理,構造函數尚未被執行完成。
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!');
});
複製代碼