NodeJS Promise 解决意外行为

NodeJS Promise resolve unexpected behavior

我有以下代码,它没有像我预期的那样工作。

const bar = () => console.log('bar')
const baz = () => console.log('baz')
const myPromise = new Promise((resolve, reject) =>
    resolve('should be right after baz, before bar')
);
const foo = () => {
    console.log('foo')
    setTimeout(bar, 0)
    myPromise.then(resolve => console.log(resolve))
    baz()
}

foo()
console.log('before bar')

结果:

foo
baz
before bar
should be right after baz, before bar
bar

根据 https://nodejs.dev/ 中的内容,promise resolve 将在函数之后立即发生。

Promises that resolve before the current function ends will be executed right after the current function.

所以我希望 'should be right after baz, before bar' 发生在 foo() 结束之后,console.log('before bar') 之前。 有人可以解释为什么没有吗?

Promise.prototype.then 的 MDN 文档指出(重点是我的):

Once a Promise is fulfilled or rejected, the respective handler function (onFulfilled or onRejected) will be called asynchronously (scheduled in the current thread loop).

您可能会发现下面的代码片段很有启发性:

Promise.resolve("I resolved").then((v) => console.log(v));
console.log("hello");

传递给 setTimeout 的回调被添加到任务队列中,而与承诺履行或拒绝相关的回调被添加到 micro-task 队列中。

这些队列仅在同步执行您的脚本后处理,即您编写的javascript代码。

因此,'before bar' 任何异步回调被调用之前 被记录。

你的代码是如何执行的?

  1. 脚本执行开始。

  2. 函数 barbazfoo 已定义。还调用了 Promise 构造函数,这导致调用 resolve 函数,同步解析 promise

  3. foo函数被调用

    1. 'foo' 登录到控制台
    2. setTimeout被调用,调度bar被异步调用。 bar将加入任务队列
    3. then 方法在 myPromise 上被调用,调度要异步调用的履行处理程序。此履行处理程序已添加到 micro-task 队列
    4. baz 函数被调用,在控制台上记录 'baz'
  4. foo 功能结束。控制 return 到调用 foo 的下一行

  5. 执行最后一个 console.log 语句,在控制台上记录 'before bar'

  6. 脚本执行结束

此时控制台输出如下图:

foo
baz
before bar

并且任务队列包含一个执行 bar 函数的任务,而 micro-task 队列包含一个 micro-task 来执行 myPromise 的执行处理程序。

脚本执行结束后,可以处理任务和micro-task队列。

处理micro-task队列:

  • 每次回调后,只要 call-stack 为空 (这是 nodejs.dev 所指)
  • 每个任务后

脚本执行是一项任务,执行后,micro-task队列将是第一个被处理的队列,在控制台上记录'should be right after baz, before bar'

最后,处理任务队列,调用baz函数,在控制台上记录'baz'


阅读以下内容可能有助于理解 micro-task 和任务队列:

如果想了解异步概念,你应该看看这个很棒的 video

我会尽力向您解释代码的顺序运行这里

首先,我们正在用函数初始化所有变量。 然后我们调用 foo(),然后调用 console.log('before baz').

调用 foo 时会发生的事情是 foo 将进入 stackfoo 登录到控制台。

然后setTimeout(bar, 0)被调用。 setTimeout(bar, 0)异步的,稍后会被调用并推送到回调队列中(这将在堆栈为空时稍后调用)。这就是为什么我们没有看到 bar.

的控制台的原因

在此之后 myPromise.then(resolve => console.log(resolve)) 被调用。这也是一个 异步 调用。所以这也将被推送到回调队列。

则执行baz()。这就是为什么我们在 foo.

之后看到 baz 的日志

现在请记住,我们在堆栈中有 console.log('before bar'),所以这将首先记录。之后回调队列中的所有项目都将被调用。

目前已记录三件事:- foo baz before bar

现在,promises 的好处是它们在回调队列中被优先,高于任何东西。所以即使 setTimeout 在我们的承诺之前被调用,如果承诺被执行 ,它将被 优先

这就是为什么在 bar 之前调用 'should be right after baz, before bar' 的原因。