微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

我对JS中异步任务调度方式的理解

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

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

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

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

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

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

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

我的问题

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

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

参考:

解决方法

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

实际上 ECMAScript 标准根本没有引入微任务任务队列:ES6 标准指定将 Promise 处理作业放在 TriggerPromiseReactions 下名为“PromiseJobs”的队列中,使用抽象进程 {{3 }} 将作业输入到宿主环境实现的作业队列中,而不规定如何处理宿主队列。

在 ECMAScript 采用之前

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

使用一个处理程序列表来解决一个承诺,如果它尚未运行,则需要启动蹦床以运行承诺作业。

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

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

开发者竞赛

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

这见证了 setImmediate polyfills 的引入,它根据浏览器中可用的内容选择了从事件循环开始回调到蹦床的最快策略。 GitHub 上的 uses the microtask queue 是此类 polyfill 的一个主要示例,其readme 非常值得一读。

在 ECMAScript 2015 中采用后的历史

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

上述 YuzuJS / setImmediate polyfill 的作者也向 TC39 委员会提交了一份意见书,指定在 ECMAScript 中应给予承诺作业高优先级。该提交最终被拒绝,因为不属于语言标准的实施问题。支持提交的参数在 YuzuJS/setImmediate 上不可用,因为它没有引用被拒绝的提案。

随后 HTML5 规范引入了浏览器中 Promise 实现的规则。 TC39's tracking site 指定它们进入微任务队列。


回答

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

  • 引入微任务队列可能是为了支持 Mutation Observer Events。

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

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

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


另见 MDN 上的 section on how to implement ECMAScipt's EnqueueJob abstract operation in host browsers

,

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

如果没有对这些队列进行分类,那么在确定如何严格按照规范对 setTimeout(...,0) 回调与 promise.then(...) 回调进行排序时,将会出现未定义的行为。

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

这一优势对 JavaScript 库开发人员特别有吸引力,他们的目标通常是生成高度优化的代码,同时在引擎之间保持一致的可观察行为。

,

我发现这篇 (https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/) 相对较旧的博文对其进行了很好的解释,并且还提供了一些关于旧浏览器版本的示例。

这个分离主要是用来

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。