nodejs之setTimeout源码解析
发布于 5 年前 作者 theanarkh 5726 次浏览 来自 分享

setTimeout是在系统启动的时候挂载的全局函数。代码在timer.js。

function setupGlobalTimeouts() {
    const timers = NativeModule.require('timers');
    global.clearImmediate = timers.clearImmediate;
    global.clearInterval = timers.clearInterval;
    global.clearTimeout = timers.clearTimeout;
    global.setImmediate = timers.setImmediate;
    global.setInterval = timers.setInterval;
    global.setTimeout = timers.setTimeout;
  }

我们先看一下setTimeout函数的代码。

function setTimeout(callback, after, arg1, arg2, arg3) {
  if (typeof callback !== 'function') {
    throw new errors.TypeError('ERR_INVALID_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;
  }
  // 新建一个对象,保存回调,超时时间等数据,是超时哈希队列的节点
  const timeout = new Timeout(callback, after, args, false, false);
  // 启动超时器
  active(timeout);
  // 返回一个对象
  return timeout;
}

其中Timeout函数在lib/internal/timer.js里定义。

function Timeout(callback, after, args, isRepeat, isUnrefed) {
  after *= 1; // coalesce to number or NaN
  this._called = false;
  this._idleTimeout = after;
  this._idlePrev = this;
  this._idleNext = this;
  this._idleStart = null;
  this._onTimeout = null;
  this._onTimeout = callback;
  this._timerArgs = args;
  this._repeat = isRepeat ? after : null;
  this._destroyed = false;

  this[unrefedSymbol] = isUnrefed;

  this[async_id_symbol] = ++async_id_fields[kAsyncIdCounter];
  this[trigger_async_id_symbol] = getDefaultTriggerAsyncId();
  if (async_hook_fields[kInit] > 0) {
    emitInit(this[async_id_symbol],
             'Timeout',
             this[trigger_async_id_symbol],
             this);
  }
}

由代码可知,首先创建一个保存相关信息的对象,然后执行active函数。

const active = exports.active = function(item) {
  // 插入一个超时对象到超时队列
  insert(item, false);
}
function insert(item, unrefed, start) {
  // 超时时间
  const msecs = item._idleTimeout;
  if (msecs < 0 || msecs === undefined) return;
  // 如果传了start则计算是否超时时以start为起点,否则取当前的时间
  if (typeof start === 'number') {
    item._idleStart = start;
  } else {
    item._idleStart = TimerWrap.now();
  }
  // 哈希队列
  const lists = unrefed === true ? unrefedLists : refedLists;
  var list = lists[msecs];
  // 没有则新建一个队列
  if (list === undefined) {
    debug('no %d list was found in insert, creating a new one', msecs);
    lists[msecs] = list = new TimersList(msecs, unrefed);
  }

 ...
  // 把超时节点插入超时队列
  L.append(list, item);
  assert(!L.isEmpty(list)); // list is not empty
}

从上面的代码可知,active一个定时器实际上是把新建的timeout对象挂载到一个哈希队列里。我们看一下这时候的内存视图。 在这里插入图片描述 当我们创建一个timerList的是时候,就会关联一个底层的定时器,执行setTimeout时传进来的时间是一样的,都会在一条队列中进行管理,该队列对应一个定时器,当定时器超时的时候,就会在该队列中找出超时节点。下面我们看一下new TimeWraper的时候发生了什么。

  TimerWrap(Environment* env, Local<Object> object) : HandleWrap(env, object,reinterpret_cast<uv_handle_t*>(&handle_),AsyncWrap::PROVIDER_TIMERWRAP) {
    int r = uv_timer_init(env->event_loop(), &handle_);
    CHECK_EQ(r, 0);
  }

其实就是初始化了一个libuv的uv_timer_t结构体。然后接着start函数做了什么操作。

 static void Start(const FunctionCallbackInfo<Value>& args) {TimerWrap* wrap = Unwrap<TimerWrap>(args.Holder());

    CHECK(HandleWrap::IsAlive(wrap));

    int64_t timeout = args[0]->IntegerValue();
    int err = uv_timer_start(&wrap->handle_, OnTimeout, timeout, 0);
    args.GetReturnValue().Set(err);
  }

就是启动了刚才初始化的定时器。并且设置了超时回调函数是OnTimeout。这时候,就等定时器超时,然后执行OnTimeout函数。所以我们继续看该函数的代码。

  const uint32_t kOnTimeout = 0;
  static void OnTimeout(uv_timer_t* handle) {
    TimerWrap* wrap = static_cast<TimerWrap*>(handle->data);
    Environment* env = wrap->env();
    HandleScope handle_scope(env->isolate());
    Context::Scope context_scope(env->context());
    wrap->MakeCallback(kOnTimeout, 0, nullptr);
  }

OnTimeout函数继续调kOnTimeout,但是该变量在time_wrapper.c中是一个整形,这是怎么回事呢?这时候需要回lib/timer.js里找答案。

const kOnTimeout = TimerWrap.kOnTimeout | 0;
// adds listOnTimeout to the C++ object prototype, as
// V8 would not inline it otherwise.
// 在TimerWrap中是0,给TimerWrap对象挂一个超时回调,每次的超时都会执行该回调
TimerWrap.prototype[kOnTimeout] = function listOnTimeout() {
  // 拿到该底层定时器关联的超时队列,看TimersList
  var list = this._list;
  var msecs = list.msecs;
  //
  if (list.nextTick) {
    list.nextTick = false;
    process.nextTick(listOnTimeoutNT, list);
    return;
  }

  debug('timeout callback %d', msecs);

  var now = TimerWrap.now();
  debug('now: %d', now);

  var diff, timer;
  // 取出队列的尾节点,即最先插入的节点,最可能超时的,TimeOut对象
  while (timer = L.peek(list)) {
    diff = now - timer._idleStart;

    // Check if this loop iteration is too early for the next timer.
    // This happens if there are more timers scheduled for later in the list.
    // 最早的节点的消逝时间小于设置的时间,说明还没超时,并且全部节点都没超时,直接返回
    if (diff < msecs) {
      // 算出最快超时的节点还需要多长时间超时
      var timeRemaining = msecs - (TimerWrap.now() - timer._idleStart);
      if (timeRemaining < 0) {
        timeRemaining = 1;
      }
      // 重新设置超时时间
      this.start(timeRemaining);
      debug('%d list wait because diff is %d', msecs, diff);
      return;
    }

    // The actual logic for when a timeout happens.
    // 当前节点已经超时    
    L.remove(timer);
    assert(timer !== L.peek(list));

    if (!timer._onTimeout) {
      if (async_hook_fields[kDestroy] > 0 && !timer._destroyed &&
            typeof timer[async_id_symbol] === 'number') {
        emitDestroy(timer[async_id_symbol]);
        timer._destroyed = true;
      }
      continue;
    }
    // 执行超时处理
    tryOnTimeout(timer, list);
  }

由上可知,TimeWrapper.c里的kOnTimeout字段已经被改写成一个函数,所以底层的定时器超时时会执行上面的代码,即从定时器队列中找到超时节点执行,直到遇到第一个未超时的节点,然后重新设置超时时间。再次启动定时器。

回到顶部