在/lib/timer.js
中定义了setTimeout:
function setTimeout(callback, after, arg1, arg2, arg3) {
validateFunction(callback, 'callback');
let 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, true);
// 将timeout插入到优先队列中
insert(timeout, timeout._idleTimeout);
return timeout;
}
在/internal/timer.js
中定义了Timeout和insert:
class Timeout {
constructor(callback, after, args, isRepeat, isRefed) {
// ...
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;
if (isRefed)
incRefCount();
this[kRefed] = isRefed;
this[kHasPrimitive] = false;
initAsyncResource(this, 'Timeout');
}
// ...
}
let nextExpiry = Infinity;
function insert(item, msecs, start = getLibuvNow()) {
msecs = MathTrunc(msecs);
item._idleStart = start;
let list = timerListMap[msecs];
if (list === undefined) {
const expiry = start + msecs;
// 将timeout对象插入到优先队列中
timerListMap[msecs] = list = new TimersList(expiry, msecs);
timerListQueue.insert(list);
// 第一次一定会执行 scheduleTimer
// 每次插入新的timeout,都会和最近一个即将过期的timeout进行对比
// 会尽快执行即将过期的timout
if (nextExpiry > expiry) {
// scheduleTimer是c++模块
// const { scheduleTimer } = internalBinding('timers');
scheduleTimer(msecs);
nextExpiry = expiry;
}
}
L.append(list, item);
}
timerListMap
和timerListQueue
何许人也?在libuv:timer中我们介绍到,libuv中的timer是存储在最小堆中,以便每次获取到最近过期的那个timer handle。同理,setTimeout的执行时机也是在它过期的时候,nodejs中用优先队列来存储timeout对象,以便获取到最近过期的timeout对象。
通常,使用二叉堆实现的优先队列获取到最小或最大值的时间复杂度为O(1)
,而查找一个对象的复杂度为O(log(n))
。nodejs中采用了hashMap
辅助的方式来将查找操作的时间复杂度降低到O(1)
。
// This is a priority queue with a custom sorting function that first compares
// the expiry times of two lists and if they're the same then compares their
// individual IDs to determine which list was created first.
const timerListQueue = new PriorityQueue(compareTimersLists, setPosition);
// Object map containing linked lists of timers, keyed and sorted by their
// duration in milliseconds.
//
// - key = time in milliseconds
// - value = linked list
const timerListMap = { __proto__: null };
同上文setImmediate中的消费immediate,在getTimerCallbacks
中同样注册了批量消费timeout对象的processTimers
:
function getTimerCallbacks(runNextTicks) {
// ...
function processTimers(now) {
debug('process timer lists %d', now);
nextExpiry = Infinity;
let list;
let ranAtLeastOneList = false;
// 在while循环中
while ((list = timerListQueue.peek()) != null) {
// 取出所有过期的timeout进行消费
if (list.expiry > now) {
nextExpiry = list.expiry;
return timeoutInfo[0] > 0 ? nextExpiry : -nextExpiry;
}
if (ranAtLeastOneList)
runNextTicks();
else
ranAtLeastOneList = true;
listOnTimeout(list, now);
}
return 0;
}
function listOnTimeout(list, now) {
const msecs = list.msecs;
debug('timeout callback %d', msecs);
let ranAtLeastOneTimer = false;
let timer;
// 将过期的timoe list中的timeout挨个取出
while ((timer = L.peek(list)) != null) {
const diff = now - timer._idleStart;
if (diff < msecs) {
list.expiry = MathMax(timer._idleStart + msecs, now + 1);
list.id = timerListId++;
timerListQueue.percolateDown(1);
debug('%d list wait because diff is %d', msecs, diff);
return;
}
if (ranAtLeastOneTimer)
runNextTicks();
else
ranAtLeastOneTimer = true;
L.remove(timer);
const asyncId = timer[async_id_symbol];
if (!timer._onTimeout) {
if (!timer._destroyed) {
timer._destroyed = true;
if (timer[kRefed])
timeoutInfo[0]--;
if (destroyHooksExist())
emitDestroy(asyncId);
}
continue;
}
emitBefore(asyncId, timer[trigger_async_id_symbol], timer);
let start;
if (timer._repeat)
start = getLibuvNow();
// 调用timeout对象的callback
try {
const args = timer._timerArgs;
if (args === undefined)
timer._onTimeout();
else
ReflectApply(timer._onTimeout, timer, args);
} finally {
// setInterval
// 会重新插入到优先队列中
if (timer._repeat && timer._idleTimeout !== -1) {
timer._idleTimeout = timer._repeat;
insert(timer, timer._idleTimeout, start);
} else if (!timer._idleNext && !timer._idlePrev && !timer._destroyed) {
timer._destroyed = true;
if (timer[kRefed])
timeoutInfo[0]--;
if (destroyHooksExist())
emitDestroy(asyncId);
}
}
emitAfter(asyncId);
}
debug('%d list empty', msecs);
if (list === timerListMap[msecs]) {
delete timerListMap[msecs];
timerListQueue.shift();
}
}
return {
processTimers
};
}
同样的,在nodejs初始化过程中会注册processTimers:
// 在nodejs初始化过程中会执行下面的代码
const { setupTimers } = internalBinding('timers');
const {
processImmediate,
processTimers,
} = internalTimers.getTimerCallbacks(runNextTicks);
setupTimers(processImmediate, processTimers);
processTimers
会被注册到timers_callback_function
中:
void SetupTimers(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsFunction());
CHECK(args[1]->IsFunction());
auto env = Environment::GetCurrent(args);
env->set_immediate_callback_function(args[0].As<Function>());
// processTimers
env->set_timers_callback_function(args[1].As<Function>());
}
最终会在Environment::RunTimers
中执行timers_callback_function
void Environment::RunTimers(uv_timer_t* handle) {
// ...
// 取出js callback
Local<Function> cb = env->timers_callback_function();
// 执行js 的callback
do {
TryCatchScope try_catch(env);
try_catch.SetVerbose(true);
ret = cb->Call(env->context(), process, 1, &arg);
} while (ret.IsEmpty() && env->can_call_into_js());
}
在创建setTimeout的insert
方法中,有调用scheduleTimer
,且必然会在第一次插入timeout对象的时候触发scheduleTimer
。scheduleTimer
逻辑如下:
void ScheduleTimer(const FunctionCallbackInfo<Value>& args) {
auto env = Environment::GetCurrent(args);
env->ScheduleTimer(args[0]->IntegerValue(env->context()).FromJust());
}
// 最终调用到这部分代码
void Environment::ScheduleTimer(int64_t duration_ms) {
if (started_cleanup_) return;
// 在libuv中,注册了RunTimers
// 在event loop的timer阶段,会执行一次RunTimers
uv_timer_start(timer_handle(), RunTimers, duration_ms, 0);
}