JavaScript 承诺、事件循环和作业队列



请考虑以下代码:

function foo() {
console.log('foo');
new Promise(
function(resolve, reject) {
setTimeout(function() {
resolve('RESOLVING');
}, 5000);
}
)
.then(
function(value) {
console.log(value);
}
);
}
foo();

我试图正确理解这里发生的事情:

  1. 在执行new Promise时,"执行器函数"直接运行,当调用setTimeout时,将计划向"事件队列"添加新条目的操作(5秒后)
  2. 由于调用then要添加到"作业队列"的操作,因此对传递的函数(记录到控制台)的调用被组织为在解决Promise后发生
  3. 当执行setTimeout回调时(在事件循环的某个刻度上),Promise被解析,并且基于第 2 点,then调用的函数参数被添加到"作业队列"并随后执行。

请注意,我说[一个"作业队列"]是因为有些东西我不确定;哪个"作业队列?"。根据我的理解,"作业队列"链接到"事件队列"上的条目。那么这会是上面示例中setTimeout条目吗? 假设在添加setTimeout的回调之前(和之后)没有其他事件添加到"事件队列"中,那么到那时主代码(对foo的调用)的条目是否已经(通常)消失(运行到完成),因此除了setTimeout之外,没有其他条目可以链接到then的"作业队列"条目?

  1. 在执行new Promise时,"执行器函数"直接运行,当调用setTimeout时,将计划向"事件队列"添加新条目的操作(5秒后)

是的。更具体地说,调用setTimeout使用浏览器的计时器机制调度计时器;大约五秒钟后,计时器机制将一个作业添加到主作业队列中,该队列将调用您的回调。

  1. 由于调用then添加到"作业队列"的操作,因此对传递的函数(记录到控制台)的调用被组织为在解决Promise后发生

右。then(使用单个参数)将履行处理程序添加到承诺(并创建它返回的另一个承诺)。当承诺解析时,调用处理程序的作业将添加到作业队列中(但它是不同的作业队列)。

  1. 当执行setTimeout回调时(在事件循环的某个刻度上),Promise被解析,并且基于点 2,then调用的函数参数被添加到"作业队列"并随后执行。

是的,但它不是同一个作业队列。

主作业队列是事件处理程序和计时器回调等内容的位置。主事件循环从队列中选取一个作业,运行它直到完成,然后选取下一个作业,依此类推,如果没有要运行的作业,则空闲。

一旦作业运行完成,就会运行另一个循环,该循环负责运行在该主作业期间计划的任何挂起的承诺作业。

在 JavaScript 规范中,主作业队列称为 ScriptJobs,promise 回调作业队列称为 PromiseJobs。在脚本作业结束时,所有已排队的 PromiseJobs 都会在下一个脚本作业之前执行。(在HTML规范中,它们的名称是"task"[或"macrotask"]和"microtask"。

是的,这确实意味着,如果作业 A 和作业 B 都排队,然后作业 A 被拾取并安排承诺回调,则该承诺回调在作业 B 运行之前运行,即使作业 B 首先排队(在主队列中)。

请注意,我说[一个"作业队列"]是因为有些事情我不确定;哪个"作业队列"?

希望我在上面涵盖了这一点。基本上:

  • 初始脚本执行、事件处理程序、计时器和requestAnimationFrame回调排队到 ScriptJobs 队列(主队列);它们是"宏任务"(或简称"任务")。
  • Promise
  • 回调将排队到 PromiseJobs 队列,该队列被处理,直到任务结束时为空。也就是说,承诺回调是"微任务"。

根据我的理解,"作业队列"链接到"事件队列"上的条目。

这些只是同一件事的不同名称。JavaScript 规范使用术语"job"和"job queue"。HTML 规范使用"任务"和"任务队列"和"事件循环"。事件循环是从脚本作业队列中选取作业。

那么这是上面示例中setTimeout条目吗?

当计时器触发时,将在脚本作业队列中计划作业。

假设在添加setTimeout的回调之前(和之后)没有其他事件添加到"事件队列"中,那么到那时主代码(对foo的调用)的条目是否已经(通常)消失(运行到完成),因此除了setTimeout之外,没有其他条目可以链接到then的"作业队列"条目?

基本上是的。让我们把它放下来:

  • 浏览器加载脚本并将作业添加到 ScriptJobs 以运行脚本的顶级代码。
  • 事件循环选取该作业并运行它:
    • 该代码定义foo并调用它。
    • foo中,您执行console.log,然后创建一个承诺。
    • promise 执行器调度一个计时器回调:这会将计时器添加到浏览器的计时器列表中。它尚未对作业进行排队。
    • then向承诺添加履行处理程序。
    • 这项工作结束了。
  • 大约五秒钟后,浏览器将一个作业添加到 ScriptJobs 以调用计时器回调。
  • 事件循环选取该作业并运行它:
    • 回调解析承诺,这会将承诺履行处理程序调用添加到 PromiseJobs 队列。
    • 该作业结束,但带有 PromiseJobs 中的条目,因此 JavaScript 引擎按顺序循环遍历这些条目: 它
      • 选取履行处理程序回调作业并运行它;该履行处理程序执行console.log
    • 这项工作现在已经完全完成。

更多探索:

  • JavaScript 规范中的作业和作业队列
  • HTML5 规范中的事件循环

最新更新