繁体   English   中英

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

[英]What was the motivation for introducing a separate microtask queue which the event loop prioritises over the task queue?

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

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

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

事件循环不断运行,有以下思考过程:

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

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

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

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

我的问题

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

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

参考:

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

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

在 ECMAScript 采用之前

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

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

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

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

开发者大赛

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

这看到了setImmediate的引入,它根据浏览器中可用的内容选择了从事件循环中启动对蹦床的回调的最快策略。 GitHub 上的 YuzuJS / setImmediate是这种 polyfill 的一个典型例子,它的readme文件非常值得一读。

被 ECMAScript 2015 采用后的历史

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

上面YuzuJS/setImmediate polyfill的作者也向 TC39 委员会提交了一份意见书,规定 promise 作业应该在 ECMAScript 中获得高优先级。 该提交最终被拒绝为不属于语言标准的实施问题。 支持提交的 Arguments 在TC39 的跟踪站点上不可用,因为它没有引用被拒绝的提案。

随后,HTML5 规范引入了在浏览器中实现 Promise 的规则。 关于如何在主机浏览器中实现 ECMAScipt 的 EnqueueJob 抽象操作的部分指定它们在微任务队列中为 go。


回答

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

  • 微任务队列可能是为了支持 Mutation Observer 事件而引入的。

  • promise 库的早期开发人员找到了在微任务队列中输入作业的方法,这样做可以最大限度地缩短确定承诺和运行 promise 反应作业之间的时间。 这在一定程度上造成了开发人员之间的竞争,但也有助于在处理完一系列异步操作中的一个步骤后尽快继续异步程序操作。

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

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


另请参阅在 JavaScript 中使用微任务和 MDN 上的 queueMicrotask()

一个优点是实现之间的可观察行为的可能差异更少。

如果未对这些队列进行分类,则在严格根据规范确定如何订购setTimeout(..., 0)回调与promise.then(...)回调时,将出现未定义的行为。

我认为将这些队列分类为微任务和“宏”任务的选择减少了由于异步竞争条件而可能出现的错误种类。

这一优势尤其吸引了 JavaScript 库开发人员,他们的目标通常是生成高度优化的代码,同时保持跨引擎的一致可观察行为。

我发现这个( https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ )相对较旧的博客文章解释得非常好,它还提供了一些旧浏览器版本的示例。

主要是,这种分离用于

  1. 提高浏览器性能
  2. 遵守 ECMAScript 标准的执行顺序
  3. 将 HTML 相关任务和“工作”/微任务相关工作分开。

我认为需要这种行为来支持浏览器上的工作人员。 工作人员无权访问 DOM,因此他们也必须为此提出一种新机制。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM