深入了解setTimeout源码之前,本有两个选择。一是通过chromium源码分析,二是通过Node.js源码分析。后来发现第一种方案的源码获取成本太大,于是从Node官网获取了几十兆的代码用来了解。
当前的Node版本为:v10.16.0
setTimeout方法定义于timers.js文件中,源码整体如下:
function setTimeout(callback, after, arg1, arg2, arg3) {
// 基础校验
if (typeof callback !== 'function') {
throw new 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;
}
// 新建Timeout对象
const timeout = new Timeout(callback, after, args, false, false);
active(timeout);
// 最后返回Timeout对象
return timeout;
}
Timeout构造函数内部如下:
// Timer constructor function.
// The entire prototype is defined in lib/timers.js
function Timeout(callback, after, args, isRepeat, isUnrefed) {
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._called = false;
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;
this._repeat = isRepeat ? after : null;
this._destroyed = false;
this[unrefedSymbol] = isUnrefed;
initAsyncResource(this, 'Timeout');
}
我们这里分析只关注两个参数:1.callback, 2.after
this._idleTimeout = after;
this._onTimeout = callback;
基本初始化完成,进入active方法。active方法的item参数为新new的Timeout对象。
// Schedule or re-schedule a timer.
// The item must have been enroll()'d first.
const active = exports.active = function(item) {
insert(item, false);
};
进入insert方法,item = Timeout对象, unrefed = false, start = undefined.
// The underlying logic for scheduling or re-scheduling a timer.
//
// Appends a timer onto the end of an existing timers list, or creates a new
// TimerWrap backed list if one does not already exist for the specified timeout
// duration.
function insert(item, unrefed, start) { // timeout, false
// 对after做校验
const msecs = item._idleTimeout;
if (msecs < 0 || msecs === undefined) return;
if (typeof start === 'number') {
item._idleStart = start;
} else {
item._idleStart = TimerWrap.now();
}
const lists = unrefed === true ? unrefedLists : refedLists;
// Use an existing list if there is one, otherwise we need to make a new one.
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);
}
if (!item[async_id_symbol] || item._destroyed) {
item._destroyed = false;
initAsyncResource(item, 'Timeout');
}
L.append(list, item); // list = timerlist, item = timeout, 增加一个节点在队列中,节点类型不同。
assert(!L.isEmpty(list)); // list is not empty
}
TimerWrap.now()方法返回的应当是当前的时间,具体的执行代码为:
static void Now(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
args.GetReturnValue().Set(env->GetNow());
}
这时,Timeout对象有三个关键属性:
item._idleTimeout = after; // 延迟多少秒执行
item._onTimeout = callback; // 延迟执行回调函数
item._idleStart = TimerWrap.now(); // 当下时间
然后进行到lists[after] = refedLists[after] = list = new TimersList(after, false);
也就是说refedLists对象的after属性对应一个TimersList对象,而refedLists对象是全局的。
function TimersList(msecs, unrefed) {
this._idleNext = this; // Create the list with the linkedlist properties to
this._idlePrev = this; // prevent any unnecessary hidden class changes.
this._unrefed = unrefed;
this.msecs = msecs;
const timer = this._timer = new TimerWrap();
timer._list = this;
if (unrefed === true)
timer.unref();
timer.start(msecs);
}
可以将TimersList对象视作为一个双向链表节点,它内部有指向上下节点的指针,当一个节点新建时,这个节点的的上下节点会指向自己。节点的内容为:
this.msecs = after;
this._timer = new TimerWrap(); // 这里的TimerWrap为一个Native对象
timer._list = this;
最后到timer.start(after),它的函数内部如下:
static void Start(const FunctionCallbackInfo<Value>& args) {
TimerWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
CHECK(HandleWrap::IsAlive(wrap));
int64_t timeout = args[0]->IntegerValue(); // 这里的timeout为js代码传入的after
int err = uv_timer_start(&wrap->handle_, OnTimeout, timeout, 0);
args.GetReturnValue().Set(err);
}
这里关键的地方是uv_timer_start,方法的内部如下:
int uv_timer_start(uv_timer_t* handle,
uv_timer_cb cb,
uint64_t timeout,
uint64_t repeat) {
uint64_t clamped_timeout;
if (cb == NULL)
return UV_EINVAL;
if (uv__is_active(handle))
uv_timer_stop(handle);
// 这里是关键。clamped_timeout的值等于当前时间加上未来要执行的时间
clamped_timeout = handle->loop->time + timeout;
if (clamped_timeout < timeout)
clamped_timeout = (uint64_t) -1;
handle->timer_cb = cb;
handle->timeout = clamped_timeout; // timeout为未来要执行的时间
handle->repeat = repeat;
/* start_id is the second index to be compared in uv__timer_cmp() */
handle->start_id = handle->loop->timer_counter++;
heap_insert(timer_heap(handle->loop),
(struct heap_node*) &handle->heap_node,
timer_less_than);
uv__handle_start(handle);
return 0;
}
这里我关注的是传入参数是怎么被操作的:
handle->timer_cb = cb;
handle->timeout = clamped_timeout;
好,到这里设置完成,我们回到insert方法内部继续向下,继续执行:
L.append(list, item); // list = timerlist, item = timeout, 增加一个节点在队列中,节点类型不同。
append方法将item追加到了list中。list对象是一个由item节点组成的双向链表。然后到这里添加结束。
你可能会疑惑,到这里就结束了?其实过程中有很多细节被我们忽略了,不过没关系。既然L.append用来追加节点,那它一定要取出节点,我们从上下文可知:
listOnTimeout方法中取出了这个节点(这个过程后面再涉及):
function listOnTimeout(handle, now) {
const list = handle._list;
const msecs = list.msecs;
debug('timeout callback %d', msecs);
debug('now: %d', now);
var diff, timer;
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;
}
handle.start(timeRemaining);
debug('%d list wait because diff is %d', msecs, diff);
return true;
}
// The actual logic for when a timeout happens.
L.remove(timer);
assert(timer !== L.peek(list));
if (!timer._onTimeout) {
if (destroyHooksExist() && !timer._destroyed &&
typeof timer[async_id_symbol] === 'number') {
emitDestroy(timer[async_id_symbol]);
timer._destroyed = true;
}
continue;
}
tryOnTimeout(timer);
}
// If `L.peek(list)` returned nothing, the list was either empty or we have
// called all of the timer timeouts.
// As such, we can remove the list and clean up the TimerWrap C++ handle.
debug('%d list empty', msecs);
assert(L.isEmpty(list));
// Either refedLists[msecs] or unrefedLists[msecs] may have been removed and
// recreated since the reference to `list` was created. Make sure they're
// the same instance of the list before destroying.
if (list._unrefed === true && list === unrefedLists[msecs]) {
delete unrefedLists[msecs];
} else if (list === refedLists[msecs]) {
delete refedLists[msecs];
}
// Do not close the underlying handle if its ownership has changed
// (e.g it was unrefed in its callback).
if (!handle[owner_symbol])
handle.close();
return true;
}
listOnTimeout方法其实是整个JS层处理队列事件的核心。方法内部的handle对象实为TimerWrap。handle._list
为TimersList。方法内的msecs为after。接下来while不断从TimersList中取timer。peek总是返回队首的数据。
然后到了关键处理阶段:
diff = now - timer._idleStart; // 计算方法执行时的差额
// 如果时间还不到,则进行:
if (diff < msecs) {
var timeRemaining = msecs - (TimerWrap.now() - timer._idleStart);
if (timeRemaining <= 0) {
timeRemaining = 1;
}
// 计算出剩余时间,再次执行Native的start方法。
handle.start(timeRemaining);
debug('%d list wait because diff is %d', msecs, diff);
return true;
}
handle.start方法的内部如下:
static void Start(const FunctionCallbackInfo<Value>& args) {
TimerWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, 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);
}
这时在执行start方法时就是一个after距离真正执行的剩余时间,再次执行uv_timer_start方法,
还记得上文中提到的这段代码吗?这段代码位于uv_timer_start方法内。
handle->timer_cb = cb;
handle->timeout = clamped_timeout;
刚刚我们并没有继续深究,现在不得不深究一下。
上面说到timeout被赋值了,那一定有地方再取出它进行执行。通过上下文可知:
void uv__run_timers(uv_loop_t* loop) {
struct heap_node* heap_node;
uv_timer_t* handle;
for (;;) {
heap_node = heap_min(timer_heap(loop));
if (heap_node == NULL)
break;
handle = container_of(heap_node, uv_timer_t, heap_node);
// 这里为处理的关键。
if (handle->timeout > loop->time)
break;
uv_timer_stop(handle);
uv_timer_again(handle);
handle->timer_cb(handle);
}
}
uv__run_timers是一个无限循环的方法,内部永远在循环执行timer_cb方法。还记得上文中提到的clamped_timeout吗,如果clamped_timeout为任务触发的时间,这里的无限循环一直在判断时间是否到期,如果到期了则会向下执行,否则一直循环。
这个方法是如何被触发的我们暂时不在这里深究。
我们从上文的代码可以知道,上文中的timer_cb是OnTimeout方法,它的内部实现如下:
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());
MaybeLocal<Value> ret;
Local<Value> args[1];
do {
// 这里是关键所在
args[0] = env->GetNow(); // 获取当前时间
ret = wrap->MakeCallback(env->timers_callback_function(), 1, args); // 执行调用
} while ((ret.IsEmpty() || ret.ToLocalChecked()->IsUndefined()) &&
!env->tick_info()->has_thrown() &&
env->can_call_into_js() &&
wrap->object()->Get(env->context(),
env->owner_symbol()).ToLocalChecked()
->IsUndefined());
}
这里的timers_callback_function()为js层传入的processTimers方法,设置的代码位于lib/timers.js文件中:
const [immediateInfo, toggleImmediateRef] =
setupTimers(processImmediate, processTimers);
processTimers内部如下:
function processTimers(now) {
if (this[owner_symbol])
return unrefdHandle(this[owner_symbol], now);
return listOnTimeout(this, now);
}
到这里是不是见过listOnTimeout方法?没错,我们回到了listOnTimeout方法调用处。这个从listOnTimeout方法到listOnTimeout方法会不断循环,直到if (diff < msecs) 条件不成立。也就是说当条件成立时才会继续执行。
真正的业务回调代码如下:
// 将这次的timer任务从队列中取出
L.remove(timer);
assert(timer !== L.peek(list));
// 这里不成立
if (!timer._onTimeout) {
if (destroyHooksExist() && !timer._destroyed &&
typeof timer[async_id_symbol] === 'number') {
emitDestroy(timer[async_id_symbol]);
timer._destroyed = true;
}
continue;
}
// 关键在于这里
tryOnTimeout(timer);
function tryOnTimeout(timer, start) {
timer._called = true;
const timerAsyncId = (typeof timer[async_id_symbol] === 'number') ?
timer[async_id_symbol] : null;
var threw = true;
if (timerAsyncId !== null)
emitBefore(timerAsyncId, timer[trigger_async_id_symbol]);
try {
ontimeout(timer, start);
threw = false;
} finally {
if (timerAsyncId !== null) {
if (!threw)
emitAfter(timerAsyncId);
if ((threw || !timer._repeat) && destroyHooksExist() &&
!timer._destroyed) {
emitDestroy(timerAsyncId);
timer._destroyed = true;
}
}
}
}
这里的关键在于ontimeout方法,该方法内部实现如下:
function ontimeout(timer, start) {
const args = timer._timerArgs;
if (typeof timer._onTimeout !== 'function')
return Promise.resolve(timer._onTimeout, args[0]);
if (start === undefined && timer._repeat)
start = TimerWrap.now();
if (!args)
timer._onTimeout();
else
Reflect.apply(timer._onTimeout, timer, args);
if (timer._repeat)
rearm(timer, start);
}
上面的方法执行javascript timer._onTimeout();
这里是真正的回调执行。
至此,一个普通的setTimeout方法执行完毕。