當Node.js啓動時會初始化event loop,每個event loop 都會包含按如下六個節點循環,nodejs事件循環和瀏覽器事件循環完全不一樣。
- 圖中的每個方框被稱作事件循環的一個"階段",這6個階段爲一輪事件循環;
階段概覽
- timers(定時器): 此階段執行那些有setTimeout() 和 setInterval()調度的回調函數;
- I/O callbacks(I/O回調):此階段會執行幾乎所有的回調函數,除了close callbacks(關閉回調) 和 那些有timers與setImmediate()調度的回調;
- idle(空轉),prepare: 此階段只在內部使用;
- poll(輪詢): 檢索新的I/O事件;在恰當的時候Node會阻塞在這個節點;
- check(檢查): setImmediate() 設置的回調會在此階段被調用;
- close callbacks(關閉事件的回調):
- 如:socket.on('close',...);此類的回調會在此階段被調用; 在事件循環每次運行之間,Node.js會檢查它是否等待異步I/O或定時器,如果沒有的話就會自動關閉;
如果event loop 進入了poll階段,且代碼未設定timer,將會發生下面的情況:
- 如果poll queue不爲空,event loop將同步的執行queue裏的callback,知道queue爲空,或執行的callback達到系統的上限;
- 如果poll queue爲空,將會發生下面的情況:
- 如果代碼已經被setImmediate()設定了callback,event loop 將結束poll階段進入check階段,並執行check階段的queue(check階段的queue是setImmediate設定的)
- 如果代碼沒有設定setImmediate(callback),event loop將阻塞在該階段等待callbacks加入poll queue,一旦達到就立即執行;
如果event loop進入了poll階段,且代碼設定了timer:
- 如果poll queue進入空狀態時(即poll階段爲空閒狀態),event loop將阻塞在該階段等待callbacks加入poll queue,一旦達到就立即執行;
setImmediate約定於setTimeout(cb,0)
-
path.resolve() 方法會把一個路徑或路徑片段的序列解析爲一個絕對路徑。
-
__dirname 總是指向當前文件夾的絕對路徑
-
__filename 總是指向當前文件的絕對路徑
-
note:
- io: 瀏覽器線程去調用一些異步的回調
- 執行代碼1
var fs = require('fs');
var path = require('path');
function someAsyncOperation(callback) {
// 花費2ms
fs.readFileSync('./read.txt', callback);
}
var timeoutScheduled = Date.now();
var fileReadTime = 0;
setTimeout(function() {
var delay = Date.now() - timeoutScheduled;
console.log(`setTimeout ${delay} ms have passed since I was sheculed`);
console.log('fileReaderTime', fileReadTime - timeoutScheduled);
}, 10);
someAsyncOperation(function() {
fileReadtime = Date.now();
while (Date.now() - fileReadTime < 20) {}
});
- 執行過程:
- setTimeout和readFile先後加入io
- setTimeout執行進入io;(需要10ms)
- readfile執行,也進入io;(需要2ms)
- 2ms之後, redfile已經讀取完畢,加入poll隊列,此時poll爲空,執行someAsyncOperation回調;
- 由於此回調有while,這裏阻塞20ms;執行完爲22ms
- 在10ms時,setTimeout不能執行,因爲js是單線程,setTimeout一直被阻塞
- 執行完之後(22ms以後),poll 進入空閒狀態
- event loop檢查timer,setTimeout回調;
- 執行結果:
- readFile執行22ms
- setTimeout執行22ms之後
- 執行代碼2
var fs = require('fs');
var path = require('path');
function someAsyncOperation(callback) {
// 花費9ms
fs.readFileSync('./read.txt', callback);
}
var timeoutScheduled = Date.now();
var fileReadTime = 0;
setTimeout(function() {
var delay = Date.now() - timeoutScheduled;
console.log(`setTimeout ${delay} ms have passed since I was sheculed`);
console.log('fileReaderTime', fileReadTime - timeoutScheduled);
}, 5);
someAsyncOperation(function() {
fileReadtime = Date.now();
while (Date.now() - fileReadTime < 20) {}
});
- 執行結果
- setTimeout執行:5ms
- readFile執行9 ~ 29ms
- 執行代碼3
- 在nodejs中,setTimeout(fn,0) === setTimeout(fn,0)
- 在瀏覽器中,setTimeout(fn,0) === setTimeout(fn,4)
setImmediate(() =>{
console.log('setImmediate')
},0)
setTimeout(() =>{
console.log('setTimeout')
},0)
// setTimeout 和 setImmediate的執行順序不確定
// 因爲event loop的啓動也是需要時間的,可能執行到poll階段時已經超過了1ms,此時setTimeout會先執行
const fs = require('fs');
const path = require('path');
fs.readFile(path.resolve(__dirname, '/read.txt'), () => {
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
}, 0);
});
// 執行順序是確定的, setImmediate,setTimeout
- 執行過程:
- 執行fs,第一輪主線程沒有timer和setImmediate
- 假設readFile需要2ms,執行回調
- 由poll進入check,setImmediate會先調用
- 在第二輪的timer階段,會執行setTimeout