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;

clearTimeout

function clearTimeout(timer) {
 if (timer && timer._onTimeout) {
   timer._onTimeout = null;
   unenroll(timer);
 }
}
复制代码

移除定时器,不进行函数跟踪

总结

以上是个人浅显的理解,刨的还不够深,道阻且长

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值