【问题标题】:In which phase of Nodejs event loop resolved promises' callbacks get executed?在 Nodejs 事件循环的哪个阶段解决了 Promise 的回调被执行?
【发布时间】:2021-10-07 07:26:10
【问题描述】:

根据this answer

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

Nodejs 文档提到 nextTickQueue 将在当前操作完成后处理,无论事件循环的当前阶段如何。这是否意味着已解决的 Promise 回调在轮询阶段和待处理回调阶段都不会执行?

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

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

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

【问题讨论】:

    标签: javascript node.js event-loop


    【解决方案1】:

    Nodejs 文档提到 nextTickQueue 将在 当前操作已完成,与当前阶段无关 事件循环。这是否意味着已解决的承诺回调确实在 既不是轮询阶段也不是待处理的回调阶段?

    这肯定是一种模棱两可的信息。它们可以在任一阶段运行,但并非特定阶段独有。

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

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

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

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

    您可以运行的示例:

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

    【讨论】:

    • 我真的不明白答案的最后一部分:这里真正发生的是第一个计时器回调运行并将回调添加到微任务队列中,该队列在回调后立即被消耗完成并在继续当前事件循环阶段之前。 此外,如果承诺没有立即解决怎么办。阶段之间会处理微任务队列吗?
    • @rayanJ 如果承诺没有立即解决,那么在这个阶段没有理由运行任何then 处理程序,因为它还没有准备好。这意味着它不会在第一个计时器或第二个计时器之间运行,除非您使用 process.nextTick 解决了承诺。一旦 Promise 解决,除非有待处理的 process.nextTick 任务,它的处理程序将在下一个堆栈上运行。
    • 请记住,promise 可以在超时回调或轮询期间解决(就像fetch promises 所做的那样)或任何其他阶段。但是,任何 then 处理程序都不会在 promise 解析的同一堆栈中运行。相反,它会尝试在下一个滴答时运行。
    【解决方案2】:

    microtasks 和 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 队列。您可以使用以下 sn-p 进行试验。这同样适用于 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 完成 nextTick 队列,Node.js 事件循环会指示 v8 运行所有 microtasks。因此,promise 回调在nextTick 队列之后执行。

    【讨论】:

      猜你喜欢
      • 2017-06-01
      • 1970-01-01
      • 2018-10-16
      • 2018-03-11
      • 2020-06-17
      • 1970-01-01
      • 1970-01-01
      • 2014-10-15
      • 2023-02-03
      相关资源
      最近更新 更多