“事件循环队列”和“作业队列”有什么区别?

IT技术 javascript es6-promise event-loop job-queue
2021-03-03 18:02:45

我无法理解以下代码是如何运行的。为什么“1”在“b”之后而“h”在“3”之后?顺序应该是:a, b, 1, 2, h, 3?一些文章说“事件循环队列”和“作业队列”之间的区别导致以下输出。但是如何?我已经阅读了ECMAScript 2015 - 8.4 Jobs and Job Queues的规范,想知道 Promise'job 是如何工作的,但这让我更加困惑。有人能帮我吗?谢谢!

var promise = new Promise(function(resolve, reject) {resolve(1)});
promise.then(function(resolve) {console.log(1)});
console.log('a');
promise.then(function(resolve) {console.log(2);});
setTimeout(function() {console.log('h')}, 0);
promise.then(function(resolve) {console.log(3)});
console.log('b');

// a
// b
// 1
// 2
// 3
// h

我知道 Promise 是异步的,但是 setTimeout(..) 异步操作的回调总是在 Promise 的异步操作之后。为什么?

6个回答

为什么“1”在“b”之后?

根据promise 规范,所有promise.then()处理程序在JS 的当前线程运行完成后被异步调用。因此,作为当前 JS 的一部分同步执行的aandb将在任何.then()处理程序之前执行,因此1将始终在aand之后执行b

一些有趣的阅读:T asks, microtasks, queues and schedules and What is the order of execution in javascript promises and Writing a JavaScript framework - Execution Timing, Beyond setTimeout


在这个线程中有一些很好的建议:Promises 在nextTick之间摆动setImmediate

我不建议依赖非链式事件的确切执行顺序。如果您想控制执行顺序 — 以某种方式重新排列回调,以便您希望稍后执行的回调取决于您希望较早执行的回调,或者实现一个队列(在后台执行相同的操作) )。

换句话说,如果您依赖于异步事件的特定时间,那么您实际上应该将它们链接到您的代码中,这样一个事件必须通过您的代码一个接一个发生,而不是依赖于实现中未指定的调度。

@JaromandaX,很可能 JS 实现的作业队列比浏览器实现的事件队列具有更高的优先级。但不是很确定。
2021-04-28 18:02:45
为什么h3:p 之后
2021-04-29 18:02:45
@JaromandaX Promise 将向作业队列添加作业,但 setTimeout 会在时间到期时将该函数置于事件循环的末尾。当一个作业添加到作业队列时,javascript 引擎将获取作业队列中的下一个作业,并且只有当作业队列为空时,它才会移动到事件循环中的下一个条目。事件循环上的每个插槽都有自己的作业队列
2021-05-11 18:02:45
@JaromandaX - 这与 OP 提出的问题略有不同。我必须做一些研究,以根据.then()处理程序与setTimeout()事件的排队方式来确定这是按规范进行的还是特定于实现的
2021-05-20 18:02:45
@JaromandaX -任务与微任务看起来排序是约定俗成的(并没有普遍遵循),而不是规范。
2021-05-20 18:02:45

在 HTML 术语中,来自同一域的一个页面或一组页面事件循环可以有多个任务队列来自同一个任务源任务总是进入同一个队列,浏览器选择下一个要使用的任务队列。

运行计时器回调的任务来自计时器任务源并进入同一队列。我们将此队列任务队列称为“A”

ECMAscript 2015 (ES6) 规范要求任务运行 Promise react回调以形成它们自己的名为“PromiseJobs”的作业队列ECMAscript 和 HTML 规范不使用相同的语言,所以让我们在概念上将 ECMA 的“Promise作业队列”等同于浏览器中的HTML任务队列“B” ——至少是与计时器使用的队列不同的队列。

理论上,浏览器可以从队列 A 或 B 中选择任务来运行,但在实践中,promise 任务队列具有更高的优先级,并且会在计时器回调运行之前清空。

这就是为什么最后记录“h”的原因。Promisethen调用已完成的Promise将作业放入Promise队列,其执行的优先级高于计时器回调。Promise队列只有console.log(3)在执行后才变空,这允许定时器回调执行。


先进的

ECMAScript 的守护者选择不在他们的规范中使用 HTML5 术语或任务队列描述,因为 ECMAScript 可以在更多环境中运行,而不仅仅是 HTML 浏览器。

Promise队列的本机实现可以使用“微任务”队列而不是单独的专用Promise任务队列。微队列作业只是在当前脚本线程和之前添加到微队列的任何任务完成后运行。

理解promise不需要微任务队列的细节。

对于缺乏原生支持Promise的浏览器(所有版本的 IE 等)的Promise polyfills 可能会使用计时器,并且在涉及Promisereact和计时器回调的顺序时,其行为方式与本机实现的方式不同。

我发现这个对于 JS 新手来说很容易理解。

这是@getify 书中的复制粘贴

用一个比喻:事件循环队列就像一个游乐园的游乐设施,一旦你完成了游乐项目,你就必须回到队伍的后面再骑一次。但工作队列就像完成骑行,然后插队并重新开始。

事件循环队列 - 对于除 promises 之外的所有异步回调,h

作业队列 - 所有与Promise相关的异步回调。1、2、3

同步 - a, b

从 Es6 开始,添加了作业队列运行时以适应Promise。new Promise()我们本地处理异步代码。setTimeout不是 javascript 的一部分,它是浏览器提供的 web api 的一部分。

现在我们有两个队列。回调队列作业队列作业队列也称为微任务队列。

关键在这里,作业队列比回调队列具有更高的优先级。所以在你的例子中,第一个同步代码被执行。

 console.log('a');  // a
 console.log('b');  // b

然后 promise 被发送到作业队列, setTimeout() 被发送到回调队列。现在事件循环,首先检查作业队列,不管 setTimeout() 设置了多长时间。由于队列实现了“先进先出”,因此它们按顺序执行,因为它们只是登录到控制台。

promise.then(function(resolve) {console.log(1)}); // 1
promise.then(function(resolve) {console.log(2)}); // 2
promise.then(function(resolve) {console.log(3)}); // 3 

作业队列清除后,事件循环检查回调队列

setTimeout(function() {console.log('h')}, 0); // h

ES6 有 2 个队列

  1. 回调队列
  2. 作业队列(微任务队列)

setTimeout 和 promise 都是异步代码。

在 setTimeout 中,我们明确指定了在后台 Web 浏览器 api 工作完成时自动运行的函数(在 setTimeout 的情况下,它是 Web 浏览器 api 的定时器功能),一旦定时器完成其工作,它将函数推送到回调队列并且必须等到js的所有同步代码完成,这就是为什么

console.log("a")
console.log("b")

首先完成

现在来到promise部分,JS中的任何promise都会做两件事

  1. 设置后台api功能
  2. 返回一个Promise对象

.then()该功能一次诺言运行指定已得到解决,但不会立即

在任务完成时,在任务队列中指定的函数.then()被推送到作业队列中

一旦 JS 中的所有同步代码完成,事件循环首先检查作业队列,然后检查回调队列 所以这就是为什么在最后记录 'h'