了解 setImmediate()

¥Understanding setImmediate()

当你想异步执行某些代码时,但要尽快执行,一种选择是使用 Node.js 提供的 setImmediate() 函数:

¥When you want to execute some piece of code asynchronously, but as soon as possible, one option is to use the setImmediate() function provided by Node.js:

setImmediate(() => {
  // run something
});

作为 setImmediate() 参数传递的任何函数都是在事件循环的下一次迭代中执行的回调。

¥Any function passed as the setImmediate() argument is a callback that's executed in the next iteration of the event loop.

setImmediate()setTimeout(() => {}, 0)(传递 0ms 超时)以及与 process.nextTick()Promise.then() 有何不同?

¥How is setImmediate() different from setTimeout(() => {}, 0) (passing a 0ms timeout), and from process.nextTick() and Promise.then()?

传递给 process.nextTick() 的函数将在当前操作结束后,在事件循环的当前迭代中执行。这意味着它将始终在 setTimeoutsetImmediate 之前执行。

¥A function passed to process.nextTick() is going to be executed on the current iteration of the event loop, after the current operation ends. This means it will always execute before setTimeout and setImmediate.

具有 0ms 延迟的 setTimeout() 回调与 setImmediate() 非常相似。执行顺序将取决于各种因素,但它们都将在事件循环的下一次迭代中运行。

¥A setTimeout() callback with a 0ms delay is very similar to setImmediate(). The execution order will depend on various factors, but they will be both run in the next iteration of the event loop.

process.nextTick 回调已添加到 process.nextTick queuePromise.then() 回调已添加到 promises microtask queuemacrotask queue 中添加了 setTimeoutsetImmediate 回调。

¥A process.nextTick callback is added to process.nextTick queue. A Promise.then() callback is added to promises microtask queue. A setTimeout, setImmediate callback is added to macrotask queue.

事件循环首先执行 process.nextTick queue 中的任务,然后执行 promises microtask queue,然后执行 macrotask queue

¥Event loop executes tasks in process.nextTick queue first, and then executes promises microtask queue, and then executes macrotask queue.

以下是显示 setImmediate()process.nextTick()Promise.then() 之间顺序的示例:

¥Here is an example to show the order between setImmediate(), process.nextTick() and Promise.then():

const baz = () => console.log('baz');
const foo = () => console.log('foo');
const zoo = () => console.log('zoo');

const start = () => {
  console.log('start');
  setImmediate(baz);
  new Promise((resolve, reject) => {
    resolve('bar');
  }).then(resolve => {
    console.log(resolve);
    process.nextTick(zoo);
  });
  process.nextTick(foo);
};

start();

// start foo bar zoo baz

此代码将首先调用 start(),然后在 process.nextTick queue 中调用 foo()。之后,它将处理 promises microtask queue,它会打印 bar 并同时在 process.nextTick queue 中添加 zoo()。然后它将调用刚刚添加的 zoo()。最后,调用 macrotask queue 中的 baz()

¥This code will first call start(), then call foo() in process.nextTick queue. After that, it will handle promises microtask queue, which prints bar and adds zoo() in process.nextTick queue at the same time. Then it will call zoo() which has just been added. In the end, the baz() in macrotask queue is called.

上述原则在 CommonJS 情况下适用,但请记住在 ES 模块中,例如 mjs 个文件,执行顺序会有所不同:

¥The principle aforementioned holds true in CommonJS cases, but keep in mind in ES Modules, e.g. mjs files, the execution order will be different:

// start bar foo zoo baz

这是因为正在加载的 ES 模块被封装为异步操作,因此整个脚本实际上已经在 promises microtask queue.txt 中了。因此,当 Promise 立即解决时,其回调将被附加到 microtask 队列中。Node.js 将尝试清除队列,直到移动到任何其他队列,因此你将看到它首先输出 bar

¥This is because the ES Module being loaded is wrapped as an asynchronous operation, and thus the entire script is actually already in the promises microtask queue. So when the promise is immediately resolved, its callback is appended to the microtask queue. Node.js will attempt to clear the queue until moving to any other queue, and hence you will see it outputs bar first.