事件循环上下文中微任务和宏任务之间的区别

Nic*_*ght 115 javascript event-loop node.js promise

我刚刚读完了Promises/A +规范并偶然发现了微任务和macrotask这两个术语:请参阅http://promisesaplus.com/#notes

我以前从未听说过这些术语,现在我很好奇它的区别是什么?

我已经尝试在网上找到一些信息,但我发现的所有内容都来自w3.org档案馆(这并不能解释我与众不同之处):http://lists.w3.org/Archives /Public/public-nextweb/2013Jul/0018.html

另外,我发现了一个名为"macrotask"的npm模块:https://www.npmjs.org/package/macrotask 同样,没有明确区别的是什么.

我所知道的是,它与事件循环有关,如https://html.spec.whatwg.org/multipage/webappapis.html#task-queuehttps://html.spec.whatwg中所述. .ORG /多页/ webappapis.html#执行-A-microtask检查点

根据WHATWG规范,我知道理论上我应该能够自己提取差异.但我确信其他人也可以从专家的简短解释中受益.

Nic*_*ght 180

事件循环的一次复制将从macrotask队列中正好处理一个任务(该队列简称为WHATWG规范中任务队列).在该宏任务完成之后,将处理所有可用的任务,即在相同的复飞周期内.在处理这些微任务时,它们可以排队甚至更多的微任务,这些微任务将一个接一个地运行,直到微任务队列耗尽为止.

这有什么实际后果?

如果微任务以递归方式排队其他微任务,则可能需要很长时间才能处理下一个宏任务.这意味着,您最终可能会在应用程序中出现阻止的UI或一些已完成的I/O空闲.

然而,至少有关的Node.js的process.nextTick功能(哪些队列microtasks),存在通过process.maxTickDepth的手段对这种阻挡一个内置的保护.这个值被设置为1000默认,砍伐的进一步处理microtasks达到此限制后,允许下一个宏任务要被处理)

那么何时使用什么?

基本上,当您需要以同步方式异步执行某些操作时(例如,当您在最近的将来执行此(微)任务时),请使用微任务.否则,坚持使用macrotasks.

例子

macrotasks : setTimeout,setInterval,setImmediate,requestAnimationFrame,I/O,UI呈现
微任务: process.nextTick,Promises,Object.observe,MutationObserver

  • 虽然事件循环中有一个微任务检查点,但这并不是大多数开发人员会遇到微任务的地方.当JS堆栈清空时,处理微任务.这可能在任务中多次发生,甚至在事件循环的渲染步骤中发生. (3认同)
  • `process.maxTickDepth` 很久以前就被删除了:https://github.com/nodejs/node/blob/d896f03578f2312aaae347de3b5a0b26882effc8/doc/changelogs/CHANGELOG_ARCHIVE.md#20130626-version-0113-unstable (3认同)
  • 您还可以使用 [queueMicrotask()](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/queueMicrotask) 方法添加新的微任务 (2认同)

Jaf*_*ake 74

我写了一篇关于此的帖子,包括互动示例https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

更新:我还讨论了这个https://www.youtube.com/watch?v=cCOL7MC4Pl0.讨论更详细,包括任务和微任务如何与渲染交互.


use*_*210 53

我认为我们不能脱离堆栈来讨论事件循环,所以:

JS 有三个“栈”:

  • 所有同步调用的标准堆栈(一个函数调用另一个函数等)
  • 用于所有具有更高优先级的异步操作的微任务队列(或作业队列或微任务堆栈)(process.nextTick、Promises、Object.observe、MutationObserver)
  • 用于所有具有较低优先级(setTimeout、setInterval、setImmediate、requestAnimationFrame、I/O、UI 渲染)的异步操作的宏任务队列(或事件队列、任务队列、宏任务队列
|=======|
| macro |
| [...] |
|       |
|=======|
| micro |
| [...] |
|       |
|=======|
| stack |
| [...] |
|       |
|=======|
Run Code Online (Sandbox Code Playgroud)

事件循环是这样工作的:

  • 从堆栈中从下到上执行所有内容,并且仅当堆栈为空时,检查上面队列中发生的事情
  • 检查微堆栈并在堆栈的帮助下执行那里的所有内容(如果需要),一个接一个微任务,直到微任务队列为空或不需要任何执行,然后才检查宏堆栈
  • 检查宏堆栈并在堆栈的帮助下执行那里的所有内容(如果需要)

如果堆栈不为空,则不会触及微堆栈。如果微堆栈不为空或不需要任何执行,则不会触及宏堆栈。

总结一下:微任务队列与宏任务队列几乎相同,但这些任务(process.nextTick、Promises、Object.observe、MutationObserver)比宏任务具有更高的优先级。

微观就像宏观,但具有更高的优先级。

在这里,您拥有理解一切的“终极”代码。


|=======|
| macro |
| [...] |
|       |
|=======|
| micro |
| [...] |
|       |
|=======|
| stack |
| [...] |
|       |
|=======|
Run Code Online (Sandbox Code Playgroud)
/* Result: stack [1] macro [8] stack [7], stack [7], stack [7] macro [2] macro [3] stack [4] micro [6] stack [4] micro [6] stack [4] micro [6] macro [5], macro [5], macro [5] -------------------- but in node in versions < 11 (older versions) you will get something different stack [1] macro [8] stack [7], stack [7], stack [7] macro [2] macro [3] stack [4], stack [4], stack [4] micro [6], micro [6], micro [6] macro [5], macro [5], macro [5] more info: https://blog.insiderattack.net/new-changes-to-timers-and-microtasks-from-node-v11-0-0-and-above-68d112743eb3 */
Run Code Online (Sandbox Code Playgroud)

  • 将队列称为堆栈是完全令人困惑的。 (19认同)
  • 但是为什么会存在这两个队列呢?微观任务和宏观任务之间的一般区别是什么? (2认同)

wen*_*ang 42

规范中的基本概念:

  • 事件循环有一个或多个任务队列.(任务队列是macrotask队列)
  • 每个事件循环都有一个微任务队列.
  • task queue = macrotask queue!= microtask queue
  • 任务可以被推入macrotask队列或微任务队列
  • 当任务被推入队列(微/宏)时,我们的意思是准备工作完成,所以任务现在可以执行.

事件循环过程模型如下:

调用堆栈为空时,执行以下步骤 -

  1. 选择任务队列中最旧的任务(任务A)
  2. 如果任务A为空(表示任务队列为空),请跳至步骤6
  3. 将"当前正在运行的任务"设置为"任务A"
  4. 运行"任务A"(表示运行回调函数)
  5. 将"当前正在运行的任务"设置为null,删除"任务A"
  6. 执行微任务队列
    • (a).选择微任务队列中最早的任务(任务x)
    • (b).if task x为null(表示微任务队列为空),跳转到步骤(g)
    • (c).将"当前正在运行的任务"设置为"任务x"
    • (d).运行"任务x"
    • (e).将"当前正在运行的任务"设置为null,删除"任务x"
    • (f).在微任务队列中选择下一个最旧的任务,跳转到步骤(b)
    • (g).finish微任务队列;
  7. 跳到第1步.

简化的流程模型如下:

  1. 运行macrotask队列中最旧的任务,然后将其删除.
  2. 运行微任务队列中的所有可用任务,然后删除它们.
  3. 下一轮:在macrotask队列中运行下一个任务(跳转步骤2)

要记住的事情:

  1. 当一个任务(在macrotask队列中)运行时,可能会注册新事件.因此可以创建新任务.下面是两个新创建的任务:
    • promiseA.then()的回调是一项任务
      • promiseA被解析/拒绝:任务将在当前轮次的事件循环中被推入微任务队列.
      • promiseA正在等待:任务将在未来的一轮事件循环中被推入微任务队列(可能是下一轮)
    • setTimeout(callback,n)的回调是一个任务,并将被推入macrotask队列,即使n为0;
  2. 微任务队列中的任务将在当前轮次中运行,而macrotask队列中的任务必须等待下一轮事件循环.
  3. 我们都知道回调"click","scroll","ajax","setTimeout"......是任务,但是我们也应该记住js代码作为一个整体在脚本标签中也是一个任务(一个macrotask).

  • @walox当前脚本执行也是一个宏任务。一旦所有同步代码完成,事件循环将优先考虑微任务而不是宏任务。与您的示例一样,脚本执行后,超时回调位于宏任务/回调队列中,承诺回调位于微任务队列中。由于一个宏任务已经完成(主脚本执行),事件循环将优先考虑承诺任务而不是超时任务。于是就有了这样的结果。 (8认同)
  • 浏览器`paint`任务怎么样?它们适合哪种类别? (6认同)
  • 我不知道我是否弄错了,但我有点不同意这个答案,微任务在宏任务之前运行。https://codepen.io/walox/pen/yLYjNRq? (5认同)
  • 这是一个很好的解释!感谢分享!。还有一件事要提到 **NodeJs**,`setImmediate()` 是宏/任务,而 `process.nextTick()` 是微/作业。 (3认同)

Yil*_*maz 13

宏任务包括键盘事件、鼠标事件、定时器事件(setTimeout)、网络事件、Html解析、更改Urletc。宏任务代表一些离散且独立的工作。微任务队列具有较高的优先级,因此宏任务将等待所有微任务先执行。

\n

微任务是更新应用程序状态的较小任务,应在浏览器继续执行其他任务(例如重新呈现 UI)之前执行。微任务包括 Promise 回调和 DOM 突变更改。微任务使我们能够在重新渲染 UI 之前执行某些操作,从而避免可能显示不一致的应用程序状态的不必要的 UI 渲染。

\n

宏任务和微任务的分离使事件循环能够对任务类型进行优先级排序;例如,优先考虑对绩效敏感的任务。

\n

在单次循环迭代中,最多处理一个宏任务(其他任务在队列中等待),而处理所有微任务。

\n
\n
    \n
  • 两个任务队列都放置在事件循环之外,以指示将任务添加到其匹配队列的操作发生在事件循环之外。否则,在执行 JavaScript 代码时发生的任何事件都将被忽略。检测和添加任务的行为是与事件循环分开完成的。

    \n
  • \n
  • 两种类型的任务一次执行一个。当任务开始执行时,它\xe2\x80\x99 会执行直至完成。只有浏览器可以停止\n任务的执行;例如,如果任务占用过多\n时间或内存。

    \n
  • \n
  • 所有微任务都应该在下一次渲染之前执行,因为它们的目标是在渲染发生之前更新应用程序状态。

    \n
  • \n
\n

浏览器通常尝试每秒渲染页面 60 次,\n人们普遍认为每秒 60 帧是动画显得流畅的速率。如果我们想要实现应用程序的平稳运行,单个任务以及该任务生成的所有微任务最好在 16 毫秒内完成。如果任务执行时间超过\n几秒钟,浏览器将显示 \xe2\x80\x9cUnresponsive script\xe2\x80\x9d\n消息。

\n
\n

参考 John Resig-JS Ninja 的秘密

\n