setImmediate函数的代码在lib/timer.js。
function setImmediate(callback, arg1, arg2, arg3) {
if (typeof callback !== 'function') {
throw new errors.TypeError('ERR_INVALID_CALLBACK');
}
var i, args;
switch (arguments.length) {
// fast cases
case 1:
break;
case 2:
args = [arg1];
break;
case 3:
args = [arg1, arg2];
break;
default:
args = [arg1, arg2, arg3];
for (i = 4; i < arguments.length; i++) {
// extend array dynamically, makes .apply run much faster in v6.0.0
args[i - 1] = arguments[i];
}
break;
}
return new Immediate(callback, args);
}
const Immediate = class Immediate {
constructor(callback, args) {
this._idleNext = null;
this._idlePrev = null;
// this must be set to null first to avoid function tracking
// on the hidden class, revisit in V8 versions after 6.2
this._onImmediate = null;
this._onImmediate = callback;
this._argv = args;
this._destroyed = false;
this[kRefed] = false;
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],
'Immediate',
this[trigger_async_id_symbol],
this);
}
this.ref();
immediateInfo[kCount]++;
immediateQueue.append(this);
}
function ImmediateList() {
this.head = null;
this.tail = null;
}
从上面的代码中我们知道,调用setImmediate函数后,nodejs会把回调和参数存在一个队列里。等待回调。然后处理队列里的每个节点。下面我们看一下处理函数的代码。
const {
Timer: TimerWrap,
setImmediateCallback,
} = process.binding('timer_wrap');
const [immediateInfo, toggleImmediateRef] = setImmediateCallback(processImmediate)
function processImmediate() {
const queue = outstandingQueue.head !== null ?
outstandingQueue : immediateQueue;
var immediate = queue.head;
const tail = queue.tail;
// Clear the linked list early in case new `setImmediate()` calls occur while
// immediate callbacks are executed
queue.head = queue.tail = null;
let count = 0;
let refCount = 0;
while (immediate !== null) {
immediate._destroyed = true;
const asyncId = immediate[async_id_symbol];
emitBefore(asyncId, immediate[trigger_async_id_symbol]);
count++;
if (immediate[kRefed])
refCount++;
immediate[kRefed] = undefined;
tryOnImmediate(immediate, tail, count, refCount);
emitAfter(asyncId);
immediate = immediate._idleNext;
}
immediateInfo[kCount] -= count;
immediateInfo[kRefCount] -= refCount;
immediateInfo[kHasOutstanding] = 0;
}
setImmediateCallback函数来自timer_wrapper.cc,我们看到nodejs执行了该函数,并且以processImmediate作为入参。所以我们来看看这个函数的代码。
static void SetImmediateCallback(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsFunction());
auto env = Environment::GetCurrent(args);
// 保存回调函数
env->set_immediate_callback_function(args[0].As<Function>());
auto toggle_ref_cb = [] (const FunctionCallbackInfo<Value>& args) {
Environment::GetCurrent(args)->ToggleImmediateRef(args[0]->IsTrue());
};
auto toggle_ref_function =
env->NewFunctionTemplate(toggle_ref_cb)->GetFunction(env->context())
.ToLocalChecked();
auto result = Array::New(env->isolate(), 2);
result->Set(env->context(), 0,
env->immediate_info()->fields().GetJSArray()).FromJust();
result->Set(env->context(), 1, toggle_ref_function).FromJust();
args.GetReturnValue().Set(result);
}
该函数就是在env里保存了回调函数的信息。set_immediate_callback_function函数是在env里用宏定义的,执行的时候对应的函数是env->immediate_callback_function(),该函数在env.cc的CheckImmediate中执行,那么CheckImmediate又在什么时候被执行呢?我们接着看。原来在 Environment::Start的时候注册了该函数。
uv_check_start(immediate_check_handle(), CheckImmediate)
uv_check_start是libuv的函数。
int uv_##name##_start(uv_##name##_t* handle, uv_##name##_cb cb) { \
// 如果已经执行过start函数则直接返回
if (uv__is_active(handle)) return 0; \
if (cb == NULL) return UV_EINVAL; \
// 把handle插入loop中相应类型的队列,loop有prepare,idle和check三个队列
QUEUE_INSERT_HEAD(&handle->loop->name##_handles, &handle->queue); \
// 挂载回调,下一轮循环的时候被执行
handle->name##_cb = cb; \
// 设置UV_HANDLE_ACTIVE标记位,并且loop中的handle数加一,init的时候只是把handle挂载到loop,start的时候handle才处于激活态
uv__handle_start(handle); \
return 0; \
}
我们从代码里可以看到,就把把一个handle插到了loop的队列里。然后在uv_run的循环中执行下面的函数。
uv__run_check(loop);
// 在每一轮循环中执行该函数,执行时机见uv_run
void uv__run_##name(uv_loop_t* loop) { \
uv_##name##_t* h; \
QUEUE queue; \
QUEUE* q; \
// 把该类型对应的队列中所有节点摘下来挂载到queue变量
QUEUE_MOVE(&loop->name##_handles, &queue); \
// 遍历队列,执行每个节点里面的函数
while (!QUEUE_EMPTY(&queue)) { \
// 取下当前待处理的节点
q = QUEUE_HEAD(&queue); \
// 取得该节点对应的整个结构体的基地址
h = QUEUE_DATA(q, uv_##name##_t, queue); \
// 把该节点移出当前队列
QUEUE_REMOVE(q); \
// 重新插入原来的队列
QUEUE_INSERT_TAIL(&loop->name##_handles, q); \
// 执行回调函数
h->name##_cb(h); \
} \
}
然后就会执行刚才注册的CheckImmediate,一直执行到nodejs的processImmediate函数。所以setImmediate的执行时机是在uv__run_check这个阶段。另外提一下的就是setImmediate和setTimeout谁先谁后的问题。这个其实是不一定的。从uv_run中我们看到执行定时器的代码比是比uv__run_check先的,但是如果我们在执行完定时器之后,uv__run_check之前,又新增了一个定时器和执行了setImmediate,那么setImmediate的回调就会先执行。