微任务与宏任务

B站影视 港台电影 2025-09-29 13:37 1

摘要:在 Node.js 中,微任务(Microtasks)和宏任务(Macrotasks)是事件循环(Event Loop)的核心组成部分,用于管理异步操作的执行顺序。这两种任务机制源于 JavaScript 的单线程模型,通过 libuv 库的底层支持,实现非阻

在 Node.js 中,微任务(Microtasks)和宏任务(Macrotasks)是事件循环(Event Loop)的核心组成部分,用于管理异步操作的执行顺序。这两种任务机制源于 JavaScript 的单线程模型,通过 libuv 库的底层支持,实现非阻塞 I/O 和高效任务调度。微任务和宏任务的区分允许开发人员精确控制异步代码的优先级,避免事件循环饥饿,并确保关键操作(如 Promise 解析)在宏任务间及时执行。这在处理高并发、网络请求或实时计算时特别有用。

Node.js 的事件循环基于 libuv 库,实现了一个阶段化的循环模型,用于处理异步任务。事件循环不是 JavaScript V8 引擎的一部分,而是 Node.js 运行时通过 C++ 实现的。循环分为多个阶段(如 timers、poll、check),每个阶段处理特定类型的宏任务。

宏任务(Macrotasks) :对应事件循环的“主要任务”,在特定阶段执行,通常涉及 I/O 或定时器。宏任务队列由 libuv 管理。 #技术分享微任务(Microtasks) :更高优先级的“子任务”,在每个宏任务后或事件循环阶段间执行。微任务队列由 V8 引擎和 Node.js 共同管理。

事件循环的执行顺序:同步代码 → 微任务队列 → 宏任务阶段(循环)。如果微任务不断添加新微任务,可能导致宏任务“饥饿”。

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');微任务场景 :立即异步:process.nextTick 用于在当前栈后执行,避免阻塞(如递归 setTimeout 的替代)。Promise 链:确保 then/catch 在 resolve 后立即执行,支持 async/await。错误处理:捕获微任务中的错误而不中断宏任务。示例:数据库事务后立即更新缓存。宏任务场景 :定时任务:setTimeout 用于延迟执行。I/O 操作:文件读写、网络请求。批处理:setImmediate 用于在 poll 阶段后执行,避免阻塞 I/O。示例:UI 更新后渲染(浏览器),或服务器响应后日志。

为什么要有这种区分?微任务允许细粒度控制,确保关键异步逻辑(如状态更新)优先;宏任务防止单线程阻塞,确保 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_; }uv_run(UV_RUN_ONCE):执行一个宏任务阶段。isolate->RunMicrotasks:调用 V8 的微任务队列(Promise callbacks)。env_->RunAndClearNextTicks:处理 Node.js 特有的 Next Tick Queue(process.nextTick)。

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); }nextTickQueue:一个数组,push nextTick 回调。processTicksAndRejections:递归清空队列,先 Next Tick,后 Microtasks (runMicrotaskQueue 调用 V8 RunMicrotasks)。

在 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)防止单线程死锁。区分允许:

优先微任务:支持 async/await 的“同步”语义。避免饥饿:宏任务确保 I/O 进展。性能:微任务递归清空,宏任务分阶段。

如果无区分,所有任务同队列,可能导致优先级混乱或循环阻塞。

微任务和宏任务是 Node.js 异步模型的基石,从简单用法到自定义调度,由浅入深地提升代码控制力。通过事件循环的阶段化和队列管理,我们可以构建高效应用。底层 libuv 与 V8 的集成,通过 uv_run 和 RunMicrotasks,确保非阻塞。

来源:墨码行者

相关推荐