JavaScript 异步回调 - Promise 和 setTimeout

JavaScript async callbacks - Promise and setTimeout

在下面的代码中:

setTimeout(() => console.log("hello"), 0);

Promise.resolve('Success!')
  .then(console.log)

我的理解应该是:

  1. setTimeout 被调用 => print hello 时间为 0 时直接添加到回调队列
  2. Promise.resolve => print Success! 已添加到回调队列

如果我没记错的话,回调队列是FIFO.

但是代码输出是:

Success!
hello

解释是什么?

有 2 个单独的队列用于处理回调。一个 macro 和一个 micro 队列。 setTimeoutmacro 队列中的项目排入队列,同时承诺解析 - 到 micro 队列。当前正在执行的宏任务(在本例中为主脚本本身)同步执行,逐行执行,直到完成。完成的那一刻,循环执行 microtask 队列中排队的所有内容,然后继续 macro 队列中的下一个项目(在你的情况下是 console.log("hello")setTimeout 排队。

基本上,流程如下所示:

  1. 脚本开始执行。

宏任务队列:[],微任务队列:[]。

    遇到
  1. setTimeout(() => console.log("hello"), 0); 导致在宏任务队列中推送一个新项目。

宏任务队列:[console.log("hello")],微任务队列:[].

  1. Promise.resolve('Success!').then(console.log) 已读。 Promise 立即解析为 Success! 并且 console.log 回调被排入微任务队列。

宏任务队列:[console.log("hello")],微任务队列:[console.log('Success!')].

  1. 脚本完成执行,因此它会在继续宏队列中的下一个任务之前检查微任务队列中是否有内容。
  2. console.log('Success!') 从微任务队列中拉出并执行。

宏任务队列:[console.log("hello")],微任务队列:[].

  1. 脚本再次检查微任务队列中是否还有其他内容。有none,所以它从宏任务队列中取出第一个可用的任务并执行它,即- console.log("hello").

宏任务队列:[],微任务队列:[]。

  1. 脚本执行完console.log("hello")后,它再次检查微任务队列中是否有任何东西。它是空的,所以它检查宏任务队列。它也是空的,所以所有排队的东西都被执行并且脚本完成。

不过,这是一个简化的解释,因为它可能会变得更加棘手。微任务队列通常主要处理 promise 回调,但您可以自己将代码入队。微任务队列中新添加的项目仍然会在下一个宏任务项目之前执行。此外,微任务可以将其他微任务排入队列,这可能导致处理微任务的无限循环。

一些有用的参考资源:

这里涉及两个不同的队列:任务队列微任务队列

使用 setTimeout 安排的回调函数添加到 任务队列 而使用承诺安排的回调添加到 微任务队列 或作业队列。

处理了一个微任务队列:

  • 只要调用堆栈为空,每次回调后。
  • 在每个任务之后。

另请注意,如果微任务队列中的微任务将另一个微任务排入队列,则该微任务也将在处理任务队列中的任何内容之前进行处理。换句话说,微任务队列将被处理直到为空,然后处理任务队列中的下一个任务。

以下代码片段显示了一个示例:

setTimeout(() => console.log('hello'), 0);

Promise.resolve('first microtask')
  .then(res => {
    console.log(res);
    return 'second microtask';
  })
  .then(console.log);


在您的代码中,setTimeout 的回调函数被添加到任务队列中,Promise.resolve 将微任务排队到微任务队列中。该队列在脚本执行结束时进行处理。这就是为什么在“hello”之前记录“success”的原因。

下图显示了代码的逐步执行:

进一步阅读的资源:

即使超时为 0,回调函数仍将添加到 Web API(从调用堆栈中提取后)。 Web API 是您无法访问的线程;您可以只调用 Ajax、Timeout 和 DOM.

Promise.resolve 安排一个微任务,而 setTimeout 安排一个宏任务。微任务在 运行 下一个宏任务之前执行。

所以在你的例子中,

Promise.resolve('Success!').then(console.log);

将在 setTimout 之前执行,因为 promises 比事件循环堆栈中的 setTimeout 回调函数具有更高的优先级。