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

2022-08-30 00:53:14

我刚刚读完了 Promises/A+ 规范,偶然发现了术语“微任务”和“宏任务”:请参阅 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 Again,它没有澄清究竟有什么区别。

我所知道的是,它与事件循环有关,如 https://html.spec.whatwg.org/multipage/webappapis.html#task-queuehttps://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint

我知道从理论上讲,鉴于这个WHATWG规范,我应该能够自己提取差异。但我相信其他人也可以从专家给出的简短解释中受益。


答案 1

事件循环的一个循环将正好有一个任务从宏任务队列中处理(此队列在WHATWG规范中简称为任务队列)。在此宏任务完成后,将处理所有可用的微任务,即在同一绕过周期内。在处理这些微任务时,它们可以对更多的微任务进行排队,这些微任务将逐个运行,直到微任务队列耗尽。

这样做的实际后果是什么?

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

但是,至少关于Node.js的process.nextTick函数(它对微任务进行排队),有一个内置的保护,可以通过process.maxTickDepth来防止这种阻塞。此值设置为默认值 1000,从而在达到此限制后减少对微任务的进一步处理,从而允许处理下一个宏任务

那么什么时候用什么呢?

基本上,当您需要以同步方式异步执行任务时(即,当您说在不久的将来执行此(微)任务时),请使用微任务。否则,请坚持宏任务

例子

macrotasks: setTimeoutsetIntervalsetImmediaterequestAnimationFrameI/O, UI rendering
microtasks: process.nextTickPromisesqueueMicrotaskMutationObserver


答案 2

规格中的基本概念:

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

事件循环进程模型如下:

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

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

简化的流程模型如下:

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

要记住的事情:

  1. 当任务(在宏任务队列中)正在运行时,可能会注册新事件。因此,可能会创建新任务。以下是两个新创建的任务:
    • promiseA.then() 的回调是一个任务
      • promiseA被解析/拒绝:任务将在当前一轮事件循环中被推送到微任务队列中。
      • promiseA处于挂起状态:任务将在未来一轮事件循环中被推送到微任务队列中(可能是下一轮)
    • setTimeout(回调,n)的回调是一个任务,会被推入宏任务队列,即使n是0;
  2. 微任务队列中的任务将在当前轮次中运行,而宏任务队列中的任务必须等待下一轮事件循环。
  3. 我们都知道“点击”,“滚动”,“ajax”,“setTimeout”的回调...是任务,但是我们也应该记住,脚本标签中的js代码作为一个整体也是一个任务(宏任务)。