我在节点 js 中有以下代码片段
Promise.resolve().then(() => {
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
});
// promise
// nextTick
我有这个片段
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
// nextTick
// promise
从 nodejs 的文档中,我读到传递给 process.nextTick() 的所有回调将在事件循环继续之前得到解决。
我很清楚,在第二个示例中,next tick 首先执行,因为它在事件循环的每次迭代之前执行。 但是为什么在第一个例子中它在 promise.resolve() 之后执行?
前言: 这只是我们可以推理的事情,因为我们知道当履行处理程序通过then
附加到它时,内部承诺
已经履行。但一般来说,在您的代码观察到它之前,请避免假设您知道承诺的状态。如果在事件发生之前内部承诺没有实现,则
nextTick
处理程序将首先被调用。
要理解为什么在
nextTick
回调之前调用第二个履行处理程序,让我们看看 event loop guide 关于 nextTick
的内容:
您可能已经注意到
没有显示在图中,即使它是异步 API 的一部分。这是因为process.nextTick()
在技术上不是事件循环的一部分。相反,process.nextTick()
将在当前操作完成后处理,而不管事件循环的当前阶段。在这里,一个操作被定义为从底层 C/C++ 处理程序的转换,并处理需要执行的 JavaScript。nextTickQueue
(我的重点)
让我们看看上面的代码做了什么:
Promise.resolve().then
在微任务中安排一个 promise fulfillment 回调,它是这样做的:
nextTick
回调我不能说我已经深入研究了相关的 Node.js 代码,但看起来没有涉及“操作转换”。微任务队列在每个 JavaScript 任务结束时被处理直到它为空。¹在这种情况下,任务是主脚本执行;清空微任务队列的循环在该任务的末尾。它选取主脚本排队的微任务,并在运行该微任务的过程中将另一个微任务排队(履行处理程序记录
"promise"
)。由于循环一直运行到微任务队列为空,因此它会在转换回 C/C++ 处理程序之前执行第二个微任务。
同样,我们只能以这种方式对此进行推理,因为我们知道(通过查看代码)当处理程序附加到它时内部承诺的状态是什么,而我们通常不会知道。 :-)
¹ 我应该注意,据我所知,这不是 Node.js 的特定行为(它在网络平台上—感谢 kaiido ),但考虑到 Node.js 项目团队正在尝试尽可能与 Web 平台保持一致,Node.js 不太可能在这方面以自己的方式发展。但目前,不幸的是,上面的 Node.js 事件循环文档没有谈论微任务。
只是为了演示一个极端的例子,这里是一些非常糟糕的代码的例子,通过重复排队微任务使事件循环挨饿 20 秒,阻止
nextTick
处理程序在微任务之前运行,直到微任务停止排队新的微任务:
// Starving the event loop for 20 seconds by scheduling microtasks
// Obviously, don't do this! :-)
console.log(new Date().toISOString());
let count = 0;
const stop = Date.now() + 20000;
const handler = () => {
if (Date.now() < stop) {
++count;
Promise.resolve().then(handler);
}
};
// Inside a microtask...
Promise.resolve().then(() => {
// ...schedule next tick callback...
process.nextTick(() => {
console.log(new Date().toISOString());
console.log(count);
});
// ...and queue nested microtasks until 20 seconds have passed
handler();
});