Node定時器詳解
阿新 • • 發佈:2018-12-03
//次輪迴圈執行 setTimeout(() => console.log(1)); setImmediate(() => console.log(2)); //本輪迴圈執行 process.nextTick(() => console.log(3)); Promise.resolve().then(() => console.log(4)); (() => console.log(5))();//自執行,只有這一行是同步任務 //5 3 4 1 2 /** *一、同步任務和非同步任務 同步任務總是比非同步任務更早執行 *二、本輪迴圈和次輪迴圈 追加在本輪迴圈的非同步任務 追加在次輪迴圈的非同步任務 本輪迴圈一定早於次輪迴圈執行 *Node規定,process.nextTick和Promise的回撥函式,追加在本輪迴圈,即同步任務一旦執行完成,就開始執行它們。而setTimeout、setInterval、setImmediate的回撥函式,追加在次輪迴圈。 *process.nextTick是所有非同步任務裡面最快執行的。如果希望非同步任務儘可能快地執行,那就使用它。 *根據語言規格,Promise物件的回撥函式,會進入非同步任務裡面的“微任務”(microtask)佇列。微任務佇列會追加在nextTick佇列之後。且只有前一個佇列全部清空以後,才會執行下一個佇列。 */ process.nextTick(() => console.log(1)); Promise.resolve().then(() => console.log(2)); process.nextTick(() => console.log(3)); Promise.resolve().then(() => console.log(4)); //1 3 2 4 /** *執行順序: 1.同步任務 2.process.nextTick() 3.微任務 */ //------------------------------------------------ /** *事件迴圈(event loop) 當node.js啟動時,它初始化事件迴圈,處理所提供的輸入指令碼,該指令碼可以進行Asynsynapi呼叫、排程計時器或呼叫process.nexttick-lrb-rrb-,然後開始處理事件迴圈。 *只有一個主執行緒,事件迴圈是在主執行緒上完成的。 *Node開始執行指令碼時,會先進行事件迴圈的初始化,但是這時事件迴圈還沒有開始,會先完成下面的事情。 同步任務 發出非同步請求 規劃定時器生效的時間 執行process.nextTick()等等 最後,上面這些事情都幹完了,事件迴圈就正式開始了。 */ /** *事件迴圈的六個階段 事件迴圈會無限次地執行,一輪又一輪。只有非同步任務的回撥函式佇列清空了,才會停止執行。 每一輪的事件迴圈,分成六個階段,這些階段會依次執行。 1.timers 2.I/O callbacks 3.idle,prepare 4.poll 5.check 6.close callbacks 每個階段都有一個先進先出的回撥函式佇列。只有一個階段的回撥函式佇列清空了,該執行的回撥函式都執行了,事件迴圈才會進入下一個階段。 (1)timers 這個是定時器階段,處理setTimeout()和setInterval()的回撥函式。進入這個階段後,主執行緒會檢查一下當前時間,是否滿足定時器的條件。如果滿足就執行回撥函式,否則就離開這個階段。 (2)I/O callbacks 除了以下操作的回撥函式,其他的回撥函式都在這個階段執行 ~setTimeout()和setInterval()的回撥函式 ~setImmediate()的回撥函式 ~用於關閉請求的回撥函式,比如socket.on('close',...) (3)idle,prepare 該階段只供libuv內部呼叫 (4)Poll 這個階段是輪詢時間,用於等待還未返回的I/O事件,比如伺服器的迴應、使用者移動滑鼠等等。 這個階段的時間會比較長,如果沒有其他非同步任務要處理(比如到期的定時器),會一直停留在這個階段,等待I/O請求返回結果。 (5)check 該階段執行setImmediate()的回撥函式 (6)close callbacks 該階斷執行關閉請求的回撥函式,比如socket.on('close',...) */ //事件迴圈示例 const fs = require("fs"); const timeoutScheduled = Date.now(); //非同步任務一:100ms後執行的定時器 setTimeout(() => { const delay = Date.now() - timeoutScheduled; console.log(`${delay}ms`) },100); //非同步任務二:檔案讀取後,執行一個用時200ms的回撥函式 fs.readFile('test.js',() => { const startCallback = Date.now(); while (Date.now() - startCallback < 200) { //什麼也不做 } }) /** 指令碼進入第一輪事件迴圈以後,沒有到期的定時器,也沒有已經可以執行的I/O回撥函式,所以會進入Poll階段,等待核心返回檔案讀取的結果。由於讀取小檔案一般不會超過100ms,所以在定時器到期之前,Poll階段就會得到結果,因此就會繼續往下執行。 第二輪事件迴圈,依然沒有到期的定時器,但是已經有了可以執行的I/O回撥函式,所以會進入I/O callbacks階段,執行fs.readFile的回撥函式。這個回撥函式需要200ms,也就是說,在它執行到一半的時候,100ms的定時器就會到期。但是必須等到這個回撥函式執行完,才會離開這個階段。 第三輪事件迴圈,已經有了到期的定時器,所以會在timers階段執行定時器。最後輸出結果哦大概是200多毫秒。 */ //---------------------------------------------- /** *setTimeout和setImmediate 由於setTimeout在timers階段執行,而setImmediate在check階段執行。所以,setTimeout會早於setImmediate完成。 */ setTimeout(() => console.log(1)); setImmediate(() => console.log(2)); /* 上面程式碼應該先輸出1,再輸出2,但是實際執行的時候,結果卻是不確定,有時還會先輸出2,再輸出1。 這是因為setTimeout的第二個引數預設為0。但是實際上,NODE做不到0毫秒,最少也需要1毫秒。根據官方文件,第二個引數的取之範圍在1毫秒到2147483647毫秒之間。也就是說,setTimeout(f,0)等同於setTimeout(f,1)。 實際執行的時候,進入事件迴圈以後,有可能到了1毫秒,也可能還沒到1毫秒,取決於系統當時的狀況。如果沒到1毫秒,那麼timers階段就會跳過,進入check階段,先執行setImmediate的回撥函式。 */ const fs = require('fs'); fs.readFile('test.js',() => { setTimeout(() => console.log(1)); setImmediate(() => console.log(2)); }) //2 1 //上面程式碼會先進入I/O callbacks階段,然後是check階段,最後才是timers階段。因此,setImmediate才會早於setTimeout執行。 setImmediate(function(){ console.log(1); process.nextTick(function(){ console.log(2); }); }); process.nextTick(function(){ console.log(3); setImmediate(function(){ console.log(4); }) }); //3 1 4 2 //兩個setImmediate在同一輪迴圈的同一個佇列裡面。只有清空了這個佇列,才會進入下一個階段。