在 Nodejs 事件循环的哪个阶段解析 promise 的回调被执行?

时间:2021-08-01 18:13:20

标签: javascript node.js event-loop

根据this answer

<块引用>

"重要的是要考虑到 then/catch 承诺中的回调是微任务,将在之后执行 nextTick 任务"

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

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

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

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

2 个答案:

答案 0 :(得分:3)

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

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

然而,从 Node 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 在循环的每个阶段结束时执行。

<块引用>

附带说明,重要的是要知道 microtasks 队列是 v8 引擎的一部分,而不是在 Node.js 运行时中维护。但是,一旦 Node.js 完成 v8 队列,Node.js 事件循环会指示 microtasks 运行所有 nextTick。因此,promise 回调在 nextTick 队列之后执行。

答案 1 :(得分:1)

<块引用>

Nodejs 文档提到 nextTickQueue 将在 当前操作已完成,无论当前处于哪个阶段 事件循环。这是否意味着已解决的承诺回调确实在 轮询阶段和回调阶段都没有?

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

<块引用>

已解决的 promise 回调将添加到 queueMicroTask 中,这将 在 nextTickQueue 之后处理(回调传递给 process.nextTick) 将在当前处理之后处理 事件循环的阶段是。

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

假设您有两个已过期的计时器,并且您处于事件循环的计时器过期阶段。这意味着在这个阶段有两个回调需要处理。让我们假设第一个计时器对已经解决的承诺调用 setTimeout(() => { console.log('first'); let p = Promise.resolve(); p.then(() => console.log('second')); p.then(() => console.log('third')); }, 1) setTimeout(() => { console.log('fourth') }, 1); (它可以立即得到它)。如果这是在阶段结束时触发的,那么第二个计时器回调将在此之前运行,因为它尚未结束阶段。

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

您可以运行的示例:

get
相关问题