Node timer 使用技巧和原始碼分析
Node.js 中的定時器函式與web瀏覽器中的定時函式API 類似,增加了一個setImmediate() 函式,它們向任務佇列新增定時任務
介紹
setTimeout(callback, delay)delay毫秒後執行回掉函式
setInterval(callback,delay)每隔delay毫秒執行一次回掉函式
setImmediate() 將在當前事件輪詢的末尾處執行。同步任務執行完後,delay不為0時首先立即執行setImmediate() 函式
console.log("1") setTimeout(func, 1000,10); function func(num) { console.log("2") } setImmediate(function(){console.log("3")}, 2000,10); console.log("4") //輸出 1 4 3 2 複製程式碼
取消定時器,引數為每個定時器函式返回的定時器物件
clearTimeout(timeout)
clearInterval(timeout)
clearImmediate(immediate)
使用
//tcp服務端 var net = require('net') var sever = net.createServer(function (connection) { //設定服務超時時間,並返回提示訊息 connection.setTimeout(1000, function () { console.log("響應超時."); connection.write('服務端響應超時了') }); setTimeout(function () { connection.on('end', function () { //console.log('客戶端關閉連線') }) connection.on('data', function (data) { console.log('服務端:收到客戶端傳送資料為' + data.toString()) }) //給客戶端響應的資料 connection.write('response hello') }, 5000) }) sever.listen(8080, function () { console.log('監聽埠:8080') }) 複製程式碼
HTTP伺服器端開始傳送響應資料到HTTP客戶端接收全部資料的這段時間, 如果超出設定時間,則表示響應超時,並返回超時提示
var net = require('net') var client = net.connect({ port: 8080 }) //設定請求超時時間 client.setTimeout(3000); //監聽超時事件 client.on('timeout', function () { console.log("請求超時") //取消請求資料,不再發送請求 client.destroy() }) //客戶端收到服務端執行的事件 client.on('data', function (data) { console.log('客戶端:收到服務端響應資料為' + data.toString()) client.end() }) //給服務端傳遞的資料 client.write('hello') client.on('end', function () { // console.log('斷開與伺服器的連線') }) 複製程式碼
客戶端設定請求超時時間,HTTP客戶端發起請求到接受到HTTP伺服器端返回響應頭的這段時間, 如果超出設定時間,就終止請求
原始碼分析
lib/timers.js
setTimeout函式定義
function setTimeout(callback, after, arg1, arg2, arg3) { //第一個引數必須為函式 if (typeof callback !== 'function') { throw new ERR_INVALID_CALLBACK(callback); } //處理引數 //將第三個以後的引數包裝成陣列 var i, args; switch (arguments.length) { // fast cases case 1: case 2: break; case 3: args = [arg1]; break; case 4: args = [arg1, arg2]; break; default: args = [arg1, arg2, arg3]; for (i = 5; i < arguments.length; i++) { // Extend array dynamically, makes .apply run much faster in v6.0.0 args[i - 2] = arguments[i]; } break; } //生成一個Timeout 物件 const timeout = new Timeout(callback, after, args, false); active(timeout); //返回一個定時器物件 return timeout; } 複製程式碼
Timeout建構函式
const timeout = new Timeout(callback, after, args, false);
lib/internal/timers.js
生成的timer例項 表示Node.js層面的定時器物件,比如 setTimeout、setInterval、setImmediate返回的物件
function Timeout(callback, after, args, isRepeat) { after *= 1; // Coalesce to number or NaN if (!(after >= 1 && after <= TIMEOUT_MAX)) { if (after > TIMEOUT_MAX) { process.emitWarning(`${after} does not fit into` + ' a 32-bit signed integer.' + '\nTimeout duration was set to 1.', 'TimeoutOverflowWarning'); } after = 1; // Schedule on next tick, follows browser behavior } //延遲時間 this._idleTimeout = after; //前後指標 this._idlePrev = this; this._idleNext = this; this._idleStart = null; // This must be set to null first to avoid function tracking // on the hidden class, revisit in V8 versions after 6.2 this._onTimeout = null; //回撥函式 this._onTimeout = callback; //函式引數 this._timerArgs = args; // setInterval的引數 this._repeat = isRepeat ? after : null; // 摧毀標記 this._destroyed = false; this[kRefed] = null; initAsyncResource(this, 'Timeout'); } 複製程式碼
Timeout.prototype.unref = function() { if (this[kRefed]) { this[kRefed] = false; decRefCount(); } return this; }; Timeout.prototype.ref = function() { if (this[kRefed] === false) { this[kRefed] = true; incRefCount(); } return this; }; 複製程式碼
在Timeout建構函式原型上新增unref,ref方法
unref方法取消setTimeout()、setInterval()...回掉函式的呼叫
ref方法恢復回掉函式的呼叫
active啟用定時器物件
啟用定時器物件,執行了insert(item, true, getLibuvNow());
function active(item) { insert(item, true, getLibuvNow()); } 複製程式碼
insert
將定時器物件新增到資料結構連結串列內
function insert(item, refed, start) { //取出當前延遲時間 let msecs = item._idleTimeout; //判斷延遲時間 if (msecs < 0 || msecs === undefined) return; // Truncate so that accuracy of sub-milisecond timers is not assumed. msecs = Math.trunc(msecs); item._idleStart = start; // Use an existing list if there is one, otherwise we need to make a new one. //根據延遲時間生成一個連結串列資料結構 var list = timerListMap[msecs]; if (list === undefined) { debug('no %d list was found in insert, creating a new one', msecs); const expiry = start + msecs; timerListMap[msecs] = list = new TimersList(expiry, msecs); timerListQueue.insert(list); if (nextExpiry > expiry) { scheduleTimer(msecs); nextExpiry = expiry; } } if (!item[async_id_symbol] || item._destroyed) { item._destroyed = false; initAsyncResource(item, 'Timeout'); } if (refed === !item[kRefed]) { if (refed) incRefCount(); else decRefCount(); } item[kRefed] = refed; // 把當前timeout物件新增到對應的連結串列上 L.append(list, item); } 複製程式碼
append()
node定時器是在生成對應list連結串列頭的時候開始觸發的
function append(list, item) { if (item._idleNext || item._idlePrev) { remove(item); } // 處理新節點的頭尾連結. item._idleNext = list._idleNext; item._idlePrev = list; // 處理list的前後指標指向 list._idleNext._idlePrev = item; list._idleNext = item; } 複製程式碼
setInterval()函式的實現與setTimeout()函式類似,只有第二個引數的處理不同
const timeout = new Timeout(callback, repeat, args, true);
// setInterval的引數
this._repeat = isRepeat ? after : null;