1. 程式人生 > >node定時器介紹

node定時器介紹

node定時器簡介

node的定時器共有四種,分別是:
setTimeout()
setInterval()
setImmediate()
process.nextTick()
前兩個是語言的標準,後兩個是node獨有的。
node的非同步任務可以分為兩種,分別是追加在 本輪迴圈 的非同步任務和追加在 次輪循壞 的非同步任務。本輪迴圈一定早於次輪循壞執行。
node規定,process.nextTickPromise的回撥函式,追加在本輪迴圈,即同步任務一旦執行完成就開始執行他們。而setTimeoutsetIntervalsetImmediate的回撥函式追加在次輪循壞

process.nextTick & 微任務

process.nextTick是在本輪迴圈執行的,而且是所有非同步任務裡面最快執行的,如果希望非同步任務儘可能快地執行那就使用它。
根據語言規格,Promise物件的回撥函式,會進入非同步任務裡面的“微任務”(microtask)佇列
微任務佇列追加在process.nextTick佇列的後面,也屬於本輪迴圈。

注:只有前一個佇列全部清空以後,才會執行下一個佇列,而且process.nextTick的回撥函式,執行都會早於Promise的。

事件迴圈的概念

首先,node只有一個主執行緒,事件迴圈是在主執行緒上完成的。
其次,Node 開始執行指令碼時,會先進行事件迴圈的初始化,但是這時事件迴圈還沒有開始,會先完成同步任務、發出非同步請求、規劃定時器生效的時間、執行process.nextTick()等等事情,然後才開始事件迴圈。事件迴圈會無限次地執行,一輪又一輪。只有非同步任務的回撥函式佇列清空了,才會停止執行。每一輪的事件迴圈,分成六個階段,這些階段會依次執行,如下圖。每個階段都有一個先進先出的回撥函式佇列。只有一個階段的回撥函式佇列清空了,該執行的回撥函式都執行了,事件迴圈才會進入下一個階段。

事件迴圈六個階段

關於 setTimeout 和 setImmediate 執行順序

一般來說,由於setTimeout在 timers 階段執行,而setImmediate在 check 階段執行。所以,setTimeout會早於setImmediate完成。但也可能存在一些特殊情況是的setImmediate早於setTimeout完成,例如

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的回撥函式。

但是,下面的程式碼一定是先輸出2,再輸出1。

const fs =require('fs');
fs.readFile('test.js',()=>{setTimeout(()=> console.log(1));setImmediate(()=> console.log(2));});

上面程式碼會先進入I/O callbacks 階段,然後是check 階段,最後才是timers階段。因此,setImmediate才會早於setTimeout執行。

總結

基於此,node定時器的執行順序大致為:同步任務->process.nextTick->promise->setTimeout->setImmediate

setTimeout(()=> console.log(1));
setImmediate(()=> console.log(2));
process.nextTick(()=> console.log(3));
Promise.resolve().then(()=> console.log(4));
(()=> console.log(5))();
process.nextTick(()=> console.log(6));

因此,上述例子輸出為 5,3,6,4,1,2




參考連結:Node定時器詳解-阮一峰的網路日誌