26

我无法理解以下代码是如何运行的。为什么“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 的异步操作之后。为什么?

4

6 回答 6

21

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

根据 Promise 规范,所有 Promise.then()处理程序在 JS 的当前线程运行完成后被异步调用。因此,作为当前 JS 的一部分同步执行的a和都将在任何处理程序之前执行,因此总是在和之后。b.then()1ab

一些有趣的阅读:T ask, microtasks, queues and schedulesWhat is the order of execution in javascript promisesWriting a JavaScript framework - Execution timing, beyond setTimeout


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

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

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

于 2016-11-30T04:56:57.847 回答
13

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

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

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

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

这就是为什么最后记录“h”的原因。Promisethen对已履行的 Promise 的调用将作业置于 Promise 队列中,该队列以比计时器回调更高的优先级执行。承诺队列仅在执行后才变为空console.log(3),这允许计时器回调执行。


先进的

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

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

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

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

于 2016-11-30T07:24:40.963 回答
4

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

这是@getify 书中的复制粘贴

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

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

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

同步 - a, b

于 2018-01-28T17:28:43.037 回答
2

ES6 有 2 个队列

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

setTimeout 和 promise 都是异步代码。

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

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

先完成

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

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

.then()指定一旦 promise 被解决后要运行的函数,但不是立即运行

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

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

于 2021-09-13T05:43:56.037 回答
2

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

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

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

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

然后将 promises 发送到作业队列,并将 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
于 2020-09-11T07:31:08.887 回答
1

在 Javascript 运行时中,作业队列是在 JavaScript 运行时使用任务队列创建的,这与任务队列非常相似,但优先于任务队列,这意味着如果作业中有任何任务,javascript 事件循环首先查看作业队列queue 事件循环将检查堆栈如果堆栈为空,它将在该事件循环之后将任务从作业队列推送到堆栈中,如果作业队列为空,则再次检查作业队列现在事件循环检查任务队列是否有任何任务被推入堆栈执行。在 es6 的 Java Script Run time 中添加作业队列,用于执行 Promise

例如 :

var promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('promise win')
    }, 4000)
})
promise.then((result) => {
    console.log(result)
})
setTimeout(() => {
    console.log('setTimeout win')
}, 4000)

输出:

承诺赢

setTimeout 获胜

解释:

setTimeout 和 Promise 都是异步执行的,并且都需要相同的时间,但是 Promise 先执行然后回调,这是因为 Promise 被移动到作业队列,而 setTimeout 从 Web Api 移动到回调队列,这基本上是在 javascript 运行时创建的异步执行任务,因此作业队列优先于任务队列

于 2021-07-29T15:34:15.340 回答