请考虑以下代码:
function foo() {
console.log('foo');
new Promise(
function(resolve, reject) {
setTimeout(function() {
resolve('RESOLVING');
}, 5000);
}
)
.then(
function(value) {
console.log(value);
}
);
}
foo();
我试图正确理解这里发生的事情:
- 在执行
new Promise
时,"执行器函数"直接运行,当调用setTimeout
时,将计划向"事件队列"添加新条目的操作(5秒后) - 由于调用
then
要添加到"作业队列"的操作,因此对传递的函数(记录到控制台)的调用被组织为在解决Promise
后发生 - 当执行
setTimeout
回调时(在事件循环的某个刻度上),Promise
被解析,并且基于第 2 点,then
调用的函数参数被添加到"作业队列"并随后执行。
请注意,我说[一个"作业队列"]是因为有些东西我不确定;哪个"作业队列?"。根据我的理解,"作业队列"链接到"事件队列"上的条目。那么这会是上面示例中setTimeout
条目吗? 假设在添加setTimeout
的回调之前(和之后)没有其他事件添加到"事件队列"中,那么到那时主代码(对foo的调用)的条目是否已经(通常)消失(运行到完成),因此除了setTimeout
之外,没有其他条目可以链接到then
的"作业队列"条目?
- 在执行
new Promise
时,"执行器函数"直接运行,当调用setTimeout
时,将计划向"事件队列"添加新条目的操作(5秒后)
是的。更具体地说,调用setTimeout
使用浏览器的计时器机制调度计时器;大约五秒钟后,计时器机制将一个作业添加到主作业队列中,该队列将调用您的回调。
- 由于调用
then
添加到"作业队列"的操作,因此对传递的函数(记录到控制台)的调用被组织为在解决Promise
后发生
右。then
(使用单个参数)将履行处理程序添加到承诺(并创建它返回的另一个承诺)。当承诺解析时,调用处理程序的作业将添加到作业队列中(但它是不同的作业队列)。
- 当执行
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 规范中的事件循环