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字段已经被改写成一个函数,所以底层的定时器超时时会执行上面的代码,即从定时器队列中找到超时节点执行,直到遇到第一个未超时的节点,然后重新设置超时时间。再次启动定时器。
mark