1. 程式人生 > >Node定時器詳解

Node定時器詳解

//次輪迴圈執行
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在同一輪迴圈的同一個佇列裡面。只有清空了這個佇列,才會進入下一個階段。