引入一个单独的微任务队列的动机是什么,事件循环优先于任务队列?

blu*_*e13 5 javascript asynchronous event-handling promise ecmascript-6

我对JS中异步任务如何调度的理解

如果我有任何错误,请纠正我:

JS 运行时引擎代理由一个事件循环驱动,它收集任何用户和其他事件,将任务排入队列以处理每个回调。

事件循环持续运行,并具有以下思考过程:

  • 执行上下文堆栈(通常被称为调用堆栈)是空的?
  • 如果是,则将微任务队列(或作业队列)中的任何微任务插入调用堆栈。继续这样做,直到微任务队列为空。
  • 如果微任务队列为空,则将任务队列(或回调队列)中最早的任务插入调用栈

因此,任务和微任务的处理方式有两个主要区别:

  • 微任务(例如承诺使用微任务队列来运行它们的回调)优先于任务(例如来自其他 Web API 的回调,例如setTimeout
  • 此外,所有微任务都在任何其他事件处理或呈现或任何其他任务发生之前完成。因此,微任务之间的应用环境基本相同。

Promise 是在ES6 2015中引入的。我假设微任务队列也在 ES6 中引入。

我的问题

引入微任务队列的动机是什么?为什么不继续使用任务队列进行 Promise 呢?

更新 #1 - 我正在寻找规范更改的明确历史原因 - 即它旨在解决的问题是什么,而不是关于微任务队列好处的固执己见的答案。

参考:

tra*_*r53 8

\n

ES6 2015 中引入了 Promise。我假设 ES6 中也引入了微任务队列。

\n
\n

实际上,ECMAScript 标准根本没有引入微任务任务队列:ES6 标准指定使用抽象进程EnqueueJob将已解决的 Promise 的 Promise 处理作业放入TriggerPromiseReactions下名为“PromiseJobs”的队列中将作业放入实现的作业队列中由主机环境决定,而不规定主机队列应如何处理。

\n

在被 ECMAScript 采用之前

\n

Promise 库是在用户空间中开发的。执行 Promise 处理程序的代码位,监视它们是否抛出或返回值并有权访问resolverejectPromise 链中下一个 Promise 的虽然 Trampoline 是 Promise 库的一部分,但它不被视为用户代码的一部分,并且使用干净堆栈调用 Promise 处理程序的声明排除了 Trampoline 占用的堆栈空间。

\n

使用处理程序列表来解决承诺以调用已解决的状态(已完成或拒绝)需要启动蹦床来运行承诺作业(如果尚未运行)。

\n

使用空堆栈启动蹦床执行的方法仅限于现有的浏览器 API setTimeout,包括setImmediateMutation Observer API。Mutation Observer使用微任务队列,这可能是其引入的原因(不确定确切的浏览器历史记录)。

\n

事件循环接口的可能性,setImmediate至少 Mozilla 从未实现过,根据 MDN,Mutation Observers 在 IE11 中可用,并且setTimeout在某些情况下会受到限制,因此即使延迟时间很长,也至少需要几毫秒来执行回调。设置为零。

\n

开发者大赛

\n

对于外部观察者来说,Promise 库开发人员相互竞争,看看谁能在 Promise 结算后以最快的时间开始执行 Promise 处理程序。

\n

这看到了polyfills的引入setImmediate,它选择了最快的策略,根据浏览器中可用的内容从事件循环中启动对蹦床的回调。GitHub 上的YuzuJS /\nsetImmediate是此类填充及其的一个主要示例readme非常值得一读。

\n

ECMAScript 2015 采用后的历史

\n

ES6 中包含了 Promise,但没有指定主机实现应给予 Promise 作业的优先级。

\n

上述YuzuJS/setImmediate polyfill的作者也向 TC39 委员会提交了一份意见书,指出在 ECMAScript 中应给予 Promise 作业高度优先级。该提交最终被拒绝,因为实施问题不属于语言标准。由于TC39 的跟踪站点没有引用被拒绝的提案,因此支持该提交的论据不可用。

\n

随后 HTML5 规范引入了浏览器中 Promise 实现的规则。本节介绍如何在主机浏览器中实现 ECMAScipt\ 的 EnqueueJob 抽象操作指定它们进入微任务队列。

\n
\n

回答

\n
\n

引入微任务队列的动机是什么?为什么不继续使用任务队列来实现承诺呢?

\n
\n
    \n
  • 引入微任务队列是为了支持 Mutation Observer 事件,Jake Archibald 在 JSConf Asia 2018 ( 24:07 )上详细介绍了这一点1 的“In the Loop”演讲中详细介绍了这一点。

    \n
  • \n
  • Promise 库的早期开发人员找到了在微任务队列中输入作业的方法,从而最大限度地减少了解决 Promise 和运行 Promise 反应作业之间的时间。在某种程度上,这造成了开发人员之间的竞争,但也有利于在处理一系列异步操作中的一个步骤完成后尽快继续异步程序操作。

    \n
  • \n
  • 通过设计,履行和拒绝处理程序可以添加到已经解决的承诺中。如果出现这种情况,则无需等待事情发生即可继续进行承诺链的下一步。这里使用微任务队列意味着下一个 Promise 处理程序是异步执行的,具有干净的堆栈,或多或少立即执行。

    \n
  • \n
\n

最终指定微任务队列的决定是由著名的开发人员和公司根据他们的专家意见做出的。虽然这可能是一个很好的选择,但这样做的绝对必要性是没有意义的。

\n
\n

另请参阅在 JavaScript 中通过queueMicrotask() 使用微任务

\n
\n

1感谢 @Minh Ngh\xc4\xa9a \ 的评论,提供了 Jake Archibald 的“In the Loop”链接 ( 0:00)演讲链接 - \xe2\x98\x86\xe2\x98\x86\xe2\ x98\x86\xe2\x98\x86\xe2\x98\x86。亮点包括

\n
    \n
  • 事件循环一次执行任务队列中的一个任务、动画队列中的所有任务(执行队列时添加的任务除外)以及微任务队列中的所有任务,直到队列为空。
  • \n
  • 对事件处理程序和 Promise 回调的棘手执行顺序的依赖可能会导致单元测试失败,因为以编程方式分派的事件会同步执行事件处理程序,而不是通过事件循环。
  • \n
\n

  • 写得很好!可惜我们找不到那个被拒绝的提案。我认为演示文稿至少应该记录在会议记录中,即使该提案不再在任何地方列出。(考虑到时间,它可能没有存储库) (2认同)