在 Node.js 中,微任务(Microtasks)和宏任务(Macrotasks)是事件循环(Event Loop)的核心组成部分,用于管理异步操作的执行顺序。这两种任务机制源于 JavaScript 的单线程模型,通过 libuv 库的底层支持,实现非阻塞 I/O 和高效任务调度。微任务和宏任务的区分允许开发人员准确控制异步代码的优先级,避免事件循环饥饿,并确保关键操作(如 Promise 解析)在宏任务间及时执行。这在处理高并发、网络请求或实时计算时特别有用。
Node.js 的事件循环基于 libuv 库,实现了一个阶段化的循环模型,用于处理异步任务。事件循环不是 JavaScript V8 引擎的一部分,而是 Node.js 运行时通过 C++ 实现的。循环分为多个阶段(如 timers、poll、check),每个阶段处理特定类型的宏任务。
事件循环的执行顺序:同步代码 → 微任务队列 → 宏任务阶段(循环)。如果微任务不断添加新微任务,可能导致宏任务“饥饿”。
Node.js 提供了两个微任务队列:Next Tick Queue(process.nextTick)和 Microtask Queue(Promise callbacks)。
微任务用于在当前宏任务结束后立即执行异步代码,确保优先级高于宏任务。微任务队列在每个事件循环阶段后清空。
使用 process.nextTick 或 Promise:
console.log('Start');
process.nextTick(() => { console.log('Next Tick'); });
Promise.resolve().then(() => { console.log('Promise Then'); });
console.log('End');
这里,同步代码先执行,然后是 Next Tick Queue,最后是 Microtask Queue。
微任务一般通过内置 API 实现,但你可以链式使用:
process.nextTick(() => {
console.log('Tick 1');
process.nextTick(() => console.log('Tick 2'));
});
Promise.resolve().then(() => { console.log('Then 1'); Promise.resolve().then(() => console.log('Then 2')); });
Next Tick 优先于 Promise then 执行,由于 Next Tick Queue 在 Microtask Queue 前清空。
微任务的关键特性:递归执行直到队列为空,避免阻塞事件循环。
宏任务对应 libuv 的事件循环阶段,如定时器回调或 I/O 完成。
使用 setTimeout 、setImmediate 或 I/O:
setTimeout(() => console.log('Timeout'), 0);
setImmediate(() => console.log('Immediate'));
fs.readFile('file.txt', (err, data) => { console.log('File Read'); });
这些在 timers、check 或 poll 阶段执行。
宏任务一般由 libuv API 调度,如自定义定时器或 socket 事件。
宏任务的关键:阶段化执行,确保 I/O 非阻塞。
5.
示例:
console.log('Sync 1');
setTimeout(() => console.log('Timeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
process.nextTick(() => console.log('Next Tick'));
console.log('Sync 2');
为什么要有这种区分?微任务允许细粒度控制,确保关键异步逻辑(如状态更新)优先;宏任务防止单线程阻塞,确保 I/O 公平调度。区分避免了所有任务同级导致的混乱,提高了异步代码的可预测性。
Node.js 的事件循环由 libuv 提供 C++ 实现,Node.js 在其上添加微任务支持。libuv 的 uv_run() 函数驱动循环,Node.js 通过 V8 集成微任务队列。以下通过 Node.js 源码展示原理。
libuv 的事件循环在 uv_run(uv_loop_t* loop, uv_run_mode mode) 中实现(libuv/src/unix/core.c 或 win/core.c):
int uv_run(uv_loop_t *loop, uv_run_mode mode) {
while (r != 0 && loop->stop_flag == 0) {
uv__update_time(loop);
uv__run_timers(loop);
r = uv__run_pending(loop);
uv__run_idle(loop);
uv__run_prepare(loop);
uv__io_poll(loop, timeout);
uv__run_check(loop);
uv__run_closing_handles(loop);
}
return r;
}
libuv 处理宏任务阶段:timers (setTimeout)、poll (I/O)、check (setImmediate) 等。Node.js 包装此循环,插入微任务执行。
在 Node.js src/node.cc 中,Run() 函数包装 uv_run,并运行微任务:
int NodeMainInstance::Run() {
Isolate* isolate = env_->isolate();
while (true) {
bool platform_finished = false;
uv_run(env_->event_loop(), UV_RUN_ONCE);
platform_finished = !v8_platform->PumpMessageLoop(isolate_data_, isolate);
env_->RunAndClearNativeImmediates(); if (EmitProcessBeforeExit(env_)) continue;
v8::MicrotasksPolicy policy = v8::MicrotasksPolicy::kExplicit; isolate->RunMicrotasks();
if (env_->TickInfo()->HasScheduledTasks()) { env_->RunAndClearNextTicks(); }
if (platform_finished && !env_->TickInfo()->HasScheduledTasks()) break; } return exit_code_; }
Next Tick Queue 优先于 V8 Microtask Queue,由于它在 RunMicrotasks 前或后检查。
在
lib/internal/process/task_queues.js 中,实现 nextTick Queue:
const { nextTickQueue } = internalBinding('task_queue');
function processTicksAndRejections() { let tock; do { while (tock = nextTickQueue.shift()) { const asyncId = tock[async_id_symbol]; emitBefore(asyncId, tock[trigger_async_id_symbol]); try { const callback = tock.callback; if (tock.args === undefined) { callback(); } else { Reflect.apply(callback, undefined, tock.args); } } finally { emitAfter(asyncId); } } runMicrotaskQueue(); } while (!nextTickQueue.isEmpty() || hasMicrotasks()); }
在
lib/internal/process/next_tick.js:
function nextTick(callback) {
if (typeof callback !== 'function') throw new TypeError('callback is not a function');
nextTickQueue.push(callback);
if (!ticking) {
setTickScheduled(true);
scheduleMicrotask();
}
}
从源码可见,微任务(Next Tick + Microtasks)在每个 uv_run 迭代后执行,确保高优先级任务(如 Promise 解析)不被宏任务(如长 I/O)延迟。宏任务阶段化(libuv)防止单线程死锁。区分允许:
如果无区分,所有任务同队列,可能导致优先级混乱或循环阻塞。
微任务和宏任务是 Node.js 异步模型的基石,从简单用法到自定义调度,由浅入深地提升代码控制力。通过事件循环的阶段化和队列管理,我们可以构建高效应用。底层 libuv 与 V8 的集成,通过 uv_run 和 RunMicrotasks,确保非阻塞。