【问题标题】:Node.js event queues, Promises, and setTimeout() -- in what sequence?Node.js 事件队列、Promises 和 setTimeout()——按什么顺序排列?
【发布时间】:2021-11-10 17:13:51
【问题描述】:

概要:

在 Node.js 事件队列中,以及像“new Promise((r) => setTimeout(r, t));”这样的代码中,setTimeout() 是现在评估的,在微队列中为 Promise 解析,还是在哪里?

详情:

我正在阅读 Distributed Systems with Node.js(Thomas Hunter II,O'Reilly,第一版的第 3 版)。它告诉我 Node.js 依次通过每个队列:

  • 轮询:适用于大多数事情,包括 I/O 回调
  • 检查:setImmediate 回调
  • 关闭:关闭连接时
  • 计时器:当 setTimeout 和 setInterval 解析时
  • 待处理:特殊系统事件

在每个队列为空后还会评估两个微队列,一个用于 promises,一个用于 nextTick()。

在本书的第 13 页,他有一个示例,其中 await 调用了一个返回“new Promise((r) => setTimeout(r, t));”的函数。本书代码为:

const sleep_st = (t) => new Promise((r) => setTimeout(r, t));
const sleep_im = () => new Promise((r) => setImmediate(r));
  
(async () => {
  setImmediate(() => console.log(1));
  console.log(2);
  await sleep_st(0);
  setImmediate(() => console.log(3));
  console.log(4);

也就是说,

setImmediate(() => console.log(1));
console.log(2);
Promise.resolve().then(() => setTimeout(() => {
  setImmediate(() => console.log(3));
  console.log(4);

我认为是这样的:

  1. 程序从 Poll 队列中的一个任务开始,第 13 页代码。它开始运行。

  2. 检查队列得到一个任务,“2”打印到控制台。

  3. “await sleep_st(0)”将调用 setTimeout,它会放置一个 Timer 队列中的任务。由于超时时间为零,所以当我们 访问定时器队列会有工作要做。 sleep_st(0) 返回一个 Promise。

  4. 这结束了轮询队列的工作。

  5. 现在结果微队列开始了。我的代码继续执行。这应该从 setImmediate() 和 console.log(4)。

这意味着控制台的输出是“2 4”。但是,这本书说正确的顺序是“2 1 4 3”。也就是说,Check 的事件队列,也许还有 Timer,都参与其中。

那么,Promise 结果微队列中会发生什么?

【问题讨论】:

  • 不确定您是如何到达Promise.resolve().then(() => setTimeout(() => { 的。你是说那行中的sleep_st(0).then(() => { 吗?
  • “你如何到达”的代码抄录自本书第13页。作者以两种方式提出了逻辑。

标签: javascript node.js async-await promise


【解决方案1】:

在 Node.js 事件队列中,以及像“new Promise((r) => setTimeout(r, t));”这样的代码中,setTimeout() 是现在评估的,在微队列中为 Promise 解析,还是在哪里?

setTimeout调用 被评估为“现在”。 (setTimeout 回调会在适当的时间段内稍后调用。)当您执行new Promise(fn) 时,Promise 构造函数会立即同步调用fn您调用@ 987654326@。这就是函数(称为 executor 函数)可以启动 promise 将报告的异步工作,作为您的两个示例(一个通过调用 setTimeout 开始工作,另一个通过调用 @ 987654328@.)

您可以通过日志轻松看到这一点:

console.log("Before");
new Promise((resolve, reject) => {
    console.log("During");
    setTimeout(() => {
        console.log("(fulfilling)");
        resolve();
    }, 10);
})
.then(
    () => {
        console.log("On fulfillment");
    },
    () => {
        console.log("On rejection");
    }
);
console.log("After");

记录

前 期间 后 (实现) 履行时

因为

  • 它在执行任何其他操作之前调用console.log("Before");
  • 在回调中同步调用new Promise并记录console.log("During");
  • 它在创建承诺并向其添加履行和拒绝处理程序后调用console.log("After");
  • 当计时器触发并履行承诺时,它会调用console.log("(fulfilling)");
  • 它在调用实现处理程序时调用console.log("On fulfillment");

关于你对序列的注释:

  • “await sleep_st(0)”将调用setTimeout

为了清楚起见,特别是 sleep_st(0) 部分称为 setTimeout。所有await 所做的只是等待sleep_st 返回的承诺调用setTimeout 解决。


您可能会发现此示例很有用,请参阅内联 cmets:

const sleep = ms => new Promise(resolve => {
    // Happens immediately and synchronously when `sleep` is called
    console.log("calling setTimeout");
    setTimeout(() => {
        // Happens later, during the timer phase
        console.log("fulfilling promise");
        resolve(); // <=== If there are any attached promise handlers,
                   //      this queues calls to them in the microtask
                   //      queue, to be done after this "macro" task
                   //      running in the timer phase is complete
    }, ms);
});

const example = async (label) => {
    // Happens synchronously and immediately when `example` is called
    await sleep(0);
    // Happens in a microtask queued by the fulfillment of the promis
    console.log(`Sleep done: ${label}`);
};

(async () => {
    await Promise.all([example("a"), example("b")]);
    // Happens in a microtask queued by fulfillment of the `Promise.all`
    // promise
    console.log("All done");
})();

输出是:

调用 setTimeout 调用 setTimeout 兑现承诺 睡眠完成:一个 兑现承诺 睡眠完成:b 全做完了

注意Sleep done: a 的代码是如何在两个任务之间执行的当前宏任务的结束。

【讨论】:

  • 以我问的形式回答我的问题是最令人满意的,对我来说很清楚。书中说“微任务队列中的回调优先于阶段正常队列中的回调,并且下一个滴答微任务队列中的回调在承诺微任务队列中的回调之前运行。”我认为“微任务在每个队列为空后运行”,但正确的是“微任务在每个队列任务之后运行,即使该队列中仍有任务。因此,resolve() 在 Timer 队列中间隙运行。
  • 关于 T.J. 自己的例子,在我看来,当访问 Timer 队列时,队列上有两个任务,超时 fcns 例如“a”和“b”。调用“a”超时 fcn,它本身调用 resolve()。 “a”超时 fcn 退出,现在查询微队列。有这个解决方案,执行超过“等待睡眠(0)”并记录“睡眠完成”。现在微队列为空(没有 nextTick() 事件),调用另一个 Timer 任务,例如“b”。或者我认为。如果我是对的,那么我可以将 Q 标记为已回答:-)
  • @Jerome - 是的,基本上就是这样。放入微任务队列的不是 rersolve,而是任何履行承诺需要触发的履行处理程序调用。但是是的,当Node进入定时器阶段时,队列中有两个定时器任务;它运行第一个运行,调用resolve;由于有一个履行处理程序等待运行,它将作为微任务排队;此时第一个计时器任务完成,因此它排队的微任务开始运行;之后,下一个计时器任务开始运行。正如您所说,关键是微任务在排队的任务结束时运行。
【解决方案2】:

当我们访问定时器队列时,会有工作要做

在到达定时器阶段之前,有检查阶段,所以在“3”之前打印“1”, 但是我在我的窗口上执行了代码,结果是 2 4 1 3,即 setTimeout 和 setImmediate 将争先恐后地执行,有时先 setTimeout,然后再执行,我在短时间内多次执行本书中的示例代码

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-08-08
    • 2013-06-28
    • 1970-01-01
    • 1970-01-01
    • 2020-02-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多