next_tick函数是process对象的一个属性。他是在bootstrap_node.js中设置的。
bootstrap_node.js
NativeModule.require('internal/process/next_tick').setup();
internal/process/next_tick
process.nextTick = nextTick;
function nextTick(callback) {
if (typeof callback !== 'function')
throw new errors.TypeError('ERR_INVALID_CALLBACK');
if (process._exiting)
return;
var args;
switch (arguments.length) {
case 1: break;
case 2: args = [arguments[1]]; break;
case 3: args = [arguments[1], arguments[2]]; break;
case 4: args = [arguments[1], arguments[2], arguments[3]]; break;
default:
args = new Array(arguments.length - 1);
for (var i = 1; i < arguments.length; i++)
args[i - 1] = arguments[i];
}
nextTickQueue.push(new TickObject(callback, args,
getDefaultTriggerAsyncId()));
}
class TickObject {
constructor(callback, args, triggerAsyncId) {
// this must be set to null first to avoid function tracking
// on the hidden class, revisit in V8 versions after 6.2
this.callback = null;
this.callback = callback;
this.args = args;
const asyncId = ++async_id_fields[kAsyncIdCounter];
this[async_id_symbol] = asyncId;
this[trigger_async_id_symbol] = triggerAsyncId;
if (async_hook_fields[kInit] > 0) {
emitInit(asyncId,
'TickObject',
triggerAsyncId,
this);
}
}
}
const nextTickQueue = {
head: null,
tail: null,
push(data) {
const entry = { data, next: null };
if (this.tail !== null) {
this.tail.next = entry;
} else {
this.head = entry;
tickInfo[kHasScheduled] = 1;
}
this.tail = entry;
},
shift() {
if (this.head === null)
return;
const ret = this.head.data;
if (this.head === this.tail) {
this.head = this.tail = null;
tickInfo[kHasScheduled] = 0;
} else {
this.head = this.head.next;
}
return ret;
}
};
我们看到执行next_tick的时候,其实就是把上下文保存到一个链表里。那我们看看什么时候会取出该链表的节点里的函数执行。首先我们看到_tickCallback函数是操作链表的函数。
function _tickCallback() {
let tock;
do {
while (tock = nextTickQueue.shift()) {
const asyncId = tock[async_id_symbol];
emitBefore(asyncId, tock[trigger_async_id_symbol]);
if (async_hook_fields[kDestroy] > 0)
emitDestroy(asyncId);
const callback = tock.callback;
if (tock.args === undefined)
callback();
else
Reflect.apply(callback, undefined, tock.args);
emitAfter(asyncId);
}
runMicrotasks();
} while (nextTickQueue.head !== null || emitPromiseRejectionWarnings());
tickInfo[kHasPromiseRejections] = 0;
}
那他什么时候会执行呢?答案在process._setupNextTickl函数里。
const [
tickInfo,
runMicrotasks
] = process._setupNextTick(_tickCallback);
而_setupNextTickl函数是在node.cc定义的一个挂载在process对象上的函数。
env->SetMethod(process, "_setupNextTick", SetupNextTick);
核心代码是
env->set_tick_callback_function(args[0].As<Function>());
set_tick_callback_function是在env中用宏定义的一个函数。他保存了一个回调函数。被保存的函数会在下面的时机被执行。
void InternalCallbackScope::Close {
env_->tick_callback_function()
}
该函数是在node.cc定义的。我们看一下什么时候会执行该函数。我们发现只有在node.cc的InternalMakeCallback函数里定义了该类的一个对象。
MaybeLocal<Value> InternalMakeCallback(...) {
InternalCallbackScope scope(env, recv, asyncContext);
...
scope.Close();
}
我们还发现只有两个地方调用了InternalMakeCallback。一个是node.cc一个是async_wrapper.cc。对应的都是MakeCallback函数。因为nodejs里很多c++类都继承了async_wrapper.cc里的AsyncWrap类。然后libuv执行nodejs的c++层回调后,c++层是通过MakeCallback执行js层的回调的。所以我们就可以知道,每次libuv执行上层回调的之后,都会执行next_tick注册的函数。执行完之后会执行runMicrotasks()函数,该函数就是v8里执行宏任务的。
Promise.resolve().then(() => {console.log(3)})
process.nextTick(() => {
console.log(1);
process.nextTick(() => {
console.log(11);
process.nextTick(() => {
console.log(111);
})
})
})
所以我们能上面的代码中Promise是最后输出的,因为他是微任务。