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

时间:2014-09-18 14:36:24

标签: javascript node.js promise event-loop

我刚读完Promise / 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-queue中所述 和https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint

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

5 个答案:

答案 0 :(得分:162)

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

这有什么实际后果?

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

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

那么何时使用什么?

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

实施例

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

答案 1 :(得分:65)

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

更新:我还谈了这个https://www.youtube.com/watch?v=cCOL7MC4Pl0。谈话更详细,包括任务和安排。微任务与渲染相互作用。

答案 2 :(得分:32)

spec中的基本概念:

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

事件循环过程模型如下:

call stack为空时,请执行以下步骤 -

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

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

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

答案 3 :(得分:20)

我认为我们不能在分离堆栈中讨论事件循环,所以:

JS具有三个“堆栈”:

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

事件循环以这种方式工作:

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

如果堆栈不为空,则不会触摸Mico堆栈。如果微堆栈不为空或不需要执行,则宏堆栈不会被触摸。

总结:微任务队列与宏任务队列几乎相同,但是那些任务(process.nextTick,Promises,Object.observe,MutationObserver)的优先级高于宏任务。

Micro就像宏一样,但是具有更高的优先级。

在这里,您具有用于理解所有内容的“最终”代码。

console.log('stack [1]');
setTimeout(() => console.log("macro [2]"), 0);
setTimeout(() => console.log("macro [3]"), 1);

const p = Promise.resolve();
for(let i = 0; i < 3; i++) p.then(() => {
    setTimeout(() => {
        console.log('stack [4]')
        setTimeout(() => console.log("macro [5]"), 0);
        p.then(() => console.log('micro [6]'));
    }, 0);
    console.log("stack [7]");
});

console.log("macro [8]");

/* 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
*/

答案 4 :(得分:3)

JavaScript 是高级单线程语言、解释型语言。这意味着它需要一个将 JS 代码转换为机器代码的解释器。解释器的意思是引擎。用于 chrome 的 V8 引擎和用于 safari 的 webkit。 每个引擎都包含内存、调用堆栈、事件循环、计时器、Web API、事件等。

事件循环:微任务和宏任务

事件循环的概念非常简单。有一个无限循环,JavaScript 引擎等待任务,执行它们然后休眠,等待更多任务

任务已设置 - 引擎处理它们 - 然后等待更多任务(同时休眠并消耗接近零的 CPU)。 可能会发生在引擎繁忙时任务到来,然后将其排队的情况。 任务形成一个队列,即所谓的“宏任务队列

微任务完全来自我们的代码。它们通常由承诺创建: .then/catch/finally 处理程序的执行成为一个微任务。微任务也在 await 的“掩护下”使用,因为它是承诺处理的另一种形式。 在每个宏任务之后,引擎会立即执行微任务队列中的所有任务,然后再运行任何其他宏任务或渲染或其他任何东西

enter image description here