【问题标题】:What is the difference between "event loop queue" and "job queue"?“事件循环队列”和“作业队列”有什么区别?
【发布时间】:2017-04-14 07:22:09
【问题描述】:

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

【问题讨论】:

  • Promise 是异步的——即使是这样的内联同步代码,.then 也是异步调用的——这就是 Promise 的作用

标签: javascript events es6-promise event-loop job-queue


【解决方案1】:

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'

【讨论】:

    【解决方案2】:

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

    例如:

    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运行时异步执行任务,因此作业队列优先于任务队列

    【讨论】:

    • 没有标点符号很难阅读大句子
    【解决方案3】:

    从 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
    

    【讨论】:

      【解决方案4】:

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

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

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


      在这个帖子中有一些很好的建议:Promises wiggle their way between nextTick and setImmediate:

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

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

      【讨论】:

      • 为什么h3 之后:p
      • @JaromandaX,很可能 JS 实现的作业队列比浏览器实现的事件队列具有更高的优先级。但不太确定。
      • @JaromandaX - 这与 OP 提出的问题略有不同。我必须做一些研究,以根据.then() 处理程序与setTimeout() 事件的排队方式来确定这是按规范还是特定于实现。
      • @JaromandaX Promise 会将作业添加到作业队列中,但 setTimeout 将在时间到期时将该函数置于事件循环的末尾。当一个作业被添加到作业队列中时,javascript引擎将在作业队列中获取下一个作业,并且只有当作业队列为空时,它才会移动到事件循环中的下一个条目。事件循环上的每个槽都有自己的作业队列
      • @JaromandaX - Tasks vs. micro-tasks。看起来排序是按照惯例(而不是普遍遵循),而不是规范。
      【解决方案5】:

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

      这是来自@getify 的书的复制粘贴

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

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

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

      同步 - a, b

      【讨论】:

        【解决方案6】:

        在 HTML 术语中,来自同一域的一个页面或一组页面的 event loop 可以有多个 task queues。来自同一个task source 的任务总是进入同一个队列,由浏览器选择下一个要使用的任务队列。

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

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

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

        这就是最后记录“h”的原因。 Promise then 对已实现的 Promise 的调用将作业置于 Promise 队列中,该队列以比计时器回调更高的优先级执行。 Promise 队列只有在 console.log(3) 被执行后才会变为空,这允许定时器回调执行。


        高级

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

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

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-07-25
          • 2020-06-23
          • 2011-11-10
          • 1970-01-01
          • 2014-05-07
          • 2010-10-26
          • 1970-01-01
          • 2018-09-18
          相关资源
          最近更新 更多