工作队列如何与Promise一起工作?

4

我正在学习JS中的Promise,想知道Promise如何与Job队列密切配合。为了解释我的困惑,我想向您展示以下代码:

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000);

}).then(function(result) {

  alert(result); // 1

  return new Promise((resolve, reject) => { // (*)
    setTimeout(() => resolve(result * 2), 1000);
  });

})

如果您查看上面的代码,那么是不是then()的回调会先被放入Job队列中,并等待promise解决?还是说仅在promise解决后,then()的回调才被推入Job队列中?

注意:不要像这样使用 alertalert 的行为是非标准的,并且在浏览器之间(甚至在同一浏览器中,取决于选项卡是否处于活动状态)有所不同。 - T.J. Crowder
2个回答

6
当需要调用Promise回调函数时,该任务不会进入标准作业队列(ScriptJobs),而是进入 PromiseJobs 队列。当 ScriptJobs 队列中的每个作业结束时,会处理 PromiseJobs 队列直到它为空。(更多内容请参见规范:Jobs and Job Queues)。
我不确定您对代码期望的输出是什么,因为您没有说明,请看一个更简单的例子:

console.log("top");
new Promise(resolve => {
    setTimeout(() => {
        console.log("timer callback");
    }, 0);
    resolve();
})
.then(() => {
    console.log("then callback 1");
})
.then(() => {
    console.log("then callback 2");
});
console.log("bottom");

可靠的输出结果为:
top
bottom
then callback 1
then callback 2
timer callback
原因是:
  1. 运行ScriptJobs工作来运行该脚本
  2. console.log("top") 运行
  3. Promise执行函数代码运行,其中:
    • 为“现在”计划定时器作业,该作业将立即或几乎立即进入ScriptJobs队列
    • 通过调用没有参数的resolve(实际上相当于调用它与undefined,不是可链式解决触发约定),来完成promise(这意味着承诺在then被调用之前已经得到履行)。
  4. 第一个then挂接第一个履行处理程序,因为promise已经实现,所以会排队一个PromiseJobs作业
  5. 第二个then挂接第二个履行处理程序(不排队作业,等待从第一个then返回的promise)
  6. console.log("bottom") 运行
  7. 当前ScriptJob作业结束
  8. 引擎处理正在等待的PromiseJobs作业(第一个履行处理程序)
  9. 输出"then callback 1"并通过返回值履行第一个then的承诺
  10. 将另一个作业排队到PromiseJobs队列以调用第二个履行处理程序
  11. 由于PromiseJobs队列不为空,因此选择下一个PromiseJob并运行
  12. 第二个履行处理程序输出"then callback 2"
  13. PromiseJobs为空,因此引擎选择下一个ScriptJob
  14. 该ScriptJob处理定时器回调并输出"timer callback"
在HTML规范中,他们使用稍微不同的术语:“任务”(或“宏任务”)表示ScriptJobs工作,“微任务”表示PromiseJobs工作(以及其他类似工作)。
关键点是:在ScriptJob完成时排队的所有PromiseJobs都将被处理,其中包括它们排队的任何PromiseJobs;只有当PromiseJobs为空时才运行下一个ScriptJob。

嗨,好的,我明白了,当 Promise 解决时,可继续执行的回调函数会被发送到作业队列中。但问题是为什么要在该回调从 Promise 获取必要数据后再发送可继续执行的回调呢?希望你明白我的意思 :) - wewq
@wewq - 对不起,我不理解这个问题。 - T.J. Crowder
@k1an - 当编辑被拒绝时,请不要再次进行相同的编辑。在实现一个 promise 时,它没有附加任何承诺反应,这意味着没有承诺反应作业需要排队。但是感谢你努力保持准确性! :-) - T.J. Crowder
then 后面的部分是回调函数,会被添加到微队列中。由于微队列具有比宏队列更高的优先级(宏队列持有 setTimeout),因此 setTimeout 的 console.log 最后才发生? - variable
@variable - 是的,没错。 - T.J. Crowder

1

我认为then()的回调只有在promise被解决后才会被推入作业队列。

如果您将第一个超时更改为3000,则运行代码并等待3秒钟以警报1。这是因为您必须等待承诺在3秒钟内解决。

您可以从这里获取答案:https://dev59.com/CV8e5IYBdhLWcg3wRItQ#30910084

promiseA.then()的回调是一个任务

  • promiseA已解决/拒绝:任务将在当前事件循环的微任务队列中推入。
  • promiseA处于未决状态:任务将在未来事件循环的微任务队列中推入(可能是下一轮)

因此,这里的微任务与您上面提到的“作业”相同,只有当promise被解决或拒绝时,回调才会被推入作业/微任务队列。


嗨,好的,那么当 Promise 被解决后,可执行的回调函数会被执行。但是为什么呢?既然我们在 Promise 解决后获取数据,那么为什么需要将可执行的回调函数与从 Promise 接收到的数据一起发送到作业队列中呢?希望你明白我的意思。 - wewq
1
抱歉,我不确定你想问什么。但是JavaScript是一种单线程语言,它只有一个主线程来执行同步代码,异步回调必须先被推入任务队列中,并且只有在主线程为空时才会被调用。你可以尝试将0传递给setTimeout函数,即使在主线程中完成同步操作后,回调仍将被调用。这基本上就是事件循环的工作原理。如果你有兴趣,YouTube上有一段经典的视频介绍了事件循环。希望这就是你想问的内容。 - Limboer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接