Node timer 使用技巧和源碼分析

前言

Node.js 中的定時器函數與web瀏覽器中的定時函數API 相似,增長了一個setImmediate() 函數,它們向任務隊列添加定時任務node

介紹

setTimeout(callback, delay) delay毫秒後執行回掉函數
setInterval(callback,delay) 每隔delay毫秒執行一次回掉函數
setImmediate() 將在當前事件輪詢的末尾處執行。同步任務執行完後,delay不爲0時首先當即執行setImmediate() 函數
web

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服務器端返回響應頭的這段時間, 若是超出設定時間,就終止請求bash

源碼分析

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方法恢復回掉函數的調用app

active激活定時器對象

激活定時器對象,執行了 insert(item, true, getLibuvNow());async

function active(item) {
 insert(item, true, getLibuvNow());
}
複製代碼

insert

將定時器對象添加到數據結構鏈表內tcp

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;

clearTimeout

function clearTimeout(timer) {
 if (timer && timer._onTimeout) {
   timer._onTimeout = null;
   unenroll(timer);
 }
}
複製代碼

移除定時器,不進行函數跟蹤

總結

以上是我的淺顯的理解,刨的還不夠深,道阻且長

相關文章
相關標籤/搜索