在 Nodejs 事件循环的哪个阶段执行已解决的承诺回调?

In which phase of Nodejs event loop resolved promises' callbacks get executed?

根据

"And the important thing is taking into account that then/catch callbacks in a promise are microtasks and will be executed after nextTick tasks"

Nodejs 文档提到 nextTickQueue 将在当前操作完成后处理,而不管事件循环的当前阶段。这是否意味着已解决的承诺回调不会在轮询阶段和未决回调阶段执行?

此外,this video 提到有一个 queueMicroTask 队列在事件循环的当前阶段之后处理。所以我现在的理解是:

已解决的承诺回调将添加到 queueMicroTask 中,它将在 nextTickQueue 之后处理(回调传递给 process.nextTick),无论事件循环的当前阶段是什么,它都将被处理。

对吗?如果不是请纠正我,我真的很困惑。

Nodejs docs mentions that nextTickQueue will be processed after the current operation is completed, regardless of the current phase of the event loop. Does that mean resolved promises callbacks does execute in neither poll phase not pending callbacks phase?

这肯定是一条模棱两可的消息。它们可以 运行 在任何一个阶段,但它们并不专用于特定阶段。

Resolved promises callbacks will be added to queueMicroTask which will be processed after the nextTickQueue (Callbacks passed to process.nextTick) which will be processed after whatever the current phase of the event loop is.

不是在每个阶段结束时,它可能在任何阶段的任何时候发生(据我所知)。

假设您有两个已过期的计时器,并且您正处于事件循环的计时器过期阶段。这意味着在此阶段有两个回调要处理。让我们假设第一个定时器调用 then 一个已经解决的承诺(它可以马上得到它)。如果这是在阶段结束时触发的,那么第二个计时器回调将在 运行 之前,因为它还不是阶段结束。

不过这里真正发生的是第一个计时器回调 运行s 并将回调添加到微任务队列中,微任务队列在回调完成之后和继续当前事件循环阶段之前立即被消耗。

你可以举个例子运行:

setTimeout(() => {
    console.log('first');
    let p = Promise.resolve();
    p.then(() => console.log('second'));
    p.then(() => console.log('third'));
}, 1)

setTimeout(() => {
    console.log('fourth')
}, 1);

微任务和 nextTicks 有一个重要的语义,这取决于节点版本。

在节点 v11 之前, nextTick 队列在事件循环的每个阶段之间执行(定时器、I/O、立即数、关闭处理程序是四个阶段)。因此,在 Node v11 之前,事件循环中的每个阶段之间也会执行 promise 回调。 (我在这里详细写过:https://blog.insiderattack.net/promises-next-ticks-and-immediates-nodejs-event-loop-part-3-9226cbe7a6aa

但是,从节点 v11 开始,只要 microtask 作为执行的一部分添加到 nextTick 队列, 事件循环就会跳转到 microtask 队列的程序。您可以使用以下代码片段对此进行试验。这同样适用于 nextTick 队列。您可以在这里阅读更多内容:https://blog.insiderattack.net/new-changes-to-timers-and-microtasks-from-node-v11-0-0-and-above-68d112743eb3

setImmediate(() => console.log('timeout1'));
setImmediate(() => {
    console.log('timeout2')
    Promise.resolve().then(() => console.log('promise'))
});
setImmediate(() => console.log('timeout3'));
setImmediate(() => console.log('timeout4'));

以上代码的输出根据Node.js版本变化如下:

$ node -v
v10.19.0
$ node test.js    
timeout1
timeout2
timeout3
timeout4
next tick

$ nvm use 11
Now using node v11.15.0 (npm v6.7.0)
$ node test.js        
timeout1
timeout2
next tick
timeout3
timeout4

因此,重要的是要知道nextTicksmicrotasks 在 Node 版本 >=11 中具有更高的优先级,因为它们有机会在事件循环的当前阶段内处理。但是在早期的 Node 版本中,nextTicksmicrotasks 在循环的每个阶段结束时执行。

On a side note, it's important to know that microtasks queue is a part of v8 engine and not maintained in Node.js runtime. However, Node.js event loop instructs v8 to run all microtasks, once Node.js finishes with the nextTick queue. Therefore, promise callbacks are executed after nextTick queue.