何时使用 queueMicrotask() 与 process.nextTick()


¥When to use queueMicrotask() vs. process.nextTick()

queueMicrotask() API 是 process.nextTick() 的替代方案,它不使用 "下一个滴答队列",而是使用与执行已解决的 promise 的 then、catch 和 finally 处理程序相同的微任务队列来推迟执行函数。

¥The queueMicrotask() API is an alternative to process.nextTick() that instead of using the "next tick queue" defers execution of a function using the same microtask queue used to execute the then, catch, and finally handlers of resolved promises.

在 Node.js 中,每次耗尽 "下一个滴答队列" 时,微任务队列都会立即耗尽。

¥Within Node.js, every time the "next tick queue" is drained, the microtask queue is drained immediately after.

因此,在 CJS 模块中,process.nextTick() 回调始终在 queueMicrotask() 回调之前运行。但是由于 ESM 模块已经作为微任务队列的一部分进行处理,因此 queueMicrotask() 回调总是在 process.nextTick() 回调之前执行,因为 Node.js 已经在清空微任务队列。

¥So in CJS modules process.nextTick() callbacks are always run before queueMicrotask() ones. However since ESM modules are processed already as part of the microtask queue, there queueMicrotask() callbacks are always exectued before process.nextTick() ones since Node.js is already in the process of draining the microtask queue.

import { nextTick } from 'node:process';

Promise.resolve().then(() => console.log('resolve'));
queueMicrotask(() => console.log('microtask'));
nextTick(() => console.log('nextTick'));
// Output:
// resolve
// microtask
// nextTickconst { nextTick } = require('node:process');

Promise.resolve().then(() => console.log('resolve'));
queueMicrotask(() => console.log('microtask'));
nextTick(() => console.log('nextTick'));
// Output:
// nextTick
// resolve
// microtask

对于大多数用户态用例,queueMicrotask() API 提供了一种可移植且可靠的延迟执行机制,该机制适用于多个 JavaScript 平台环境,应该优于 process.nextTick()。在简单的场景中,queueMicrotask() 可以直接替代 process.nextTick()

¥For most userland use cases, the queueMicrotask() API provides a portable and reliable mechanism for deferring execution that works across multiple JavaScript platform environments and should be favored over process.nextTick(). In simple scenarios, queueMicrotask() can be a drop-in replacement for process.nextTick().

console.log('start');
queueMicrotask(() => {
  console.log('microtask callback');
});
console.log('scheduled');
// Output:
// start
// scheduled
// microtask callback 

两个 API 之间一个值得注意的区别是 process.nextTick() 允许指定额外值,这些值将在调用时作为参数传递给延迟函数。使用 queueMicrotask() 实现相同的结果需要使用闭包或绑定函数:

¥One note-worthy difference between the two APIs is that process.nextTick() allows specifying additional values that will be passed as arguments to the deferred function when it is called. Achieving the same result with queueMicrotask() requires using either a closure or a bound function:

function deferred(a, b) {
  console.log('microtask', a + b);
}

console.log('start');
queueMicrotask(deferred.bind(undefined, 1, 2));
console.log('scheduled');
// Output:
// start
// scheduled
// microtask 3 

从下一个滴答队列和微任务队列中引发的错误的处理方式存在细微差别。在排队的微任务回调中抛出的错误应该在可能的情况下在排队的回调中处理。如果不是,则可以使用 process.on('uncaughtException') 事件句柄来捕获和处理错误。

¥There are minor differences in the way errors raised from within the next tick queue and microtask queue are handled. Errors thrown within a queued microtask callback should be handled within the queued callback when possible. If they are not, the process.on('uncaughtException') event handler can be used to capture and handle the errors.

如有疑问,除非需要 process.nextTick() 的特定功能,否则请使用 queueMicrotask()

¥When in doubt, unless the specific capabilities of process.nextTick() are needed, use queueMicrotask().