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

carvr3hs  于 2022-12-12  发布在  Node.js
关注(0)|答案(6)|浏览(124)

我刚刚阅读Promises/A+规范,偶然发现了微任务和宏任务这两个术语:请http://promisesaplus.com/#notes
我以前从未听说过这些术语,现在我很好奇它们之间的区别是什么?
我已经试着在网上找到一些信息,但我所找到的只是www.example.com档案中的这篇文章w3.org(这并不能向我解释两者的区别):http://lists.w3.org/Archives/Public/public-nextweb/2013Jul/0018.html
此外,我还发现了一个名为“macrotask”的npm模块:https://www.npmjs.org/package/macrotask同样,没有明确区别到底是什么。
我所知道的是,它与事件循环有关,如www.example.com和www.example.com中所述https://html.spec.whatwg.org/multipage/webappapis.html#task-queuehttps://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
我知道理论上我应该能够自己提取差异,给出这个WHATWG规范。但我相信其他人也可以受益于Maven给出的简短解释。

bkhjykvo

bkhjykvo1#

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

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

如果一个微任务递归地将其他微任务排队,可能需要很长时间才能处理下一个宏任务。这意味着,你可能会以一个被阻塞的UI结束,或者一些完成的I/O在你的应用程序中空闲。
然而,至少关于Node.js的process.nextTick函数(它将微任务排队),存在通过process. maxTickDepth防止这种阻塞的内置保护。该值被设置为默认值1000,在达到允许处理下一个macrotask的限制后,减少了对微任务的进一步处理)

那么,何时使用什么呢?

基本上,当你需要以同步的方式异步地做一些事情时,使用微任务(例如,当你说 * 在最近的将来执行这个(微)任务 * 时)。否则,坚持使用宏任务
示例

宏任务:setTimeoutsetIntervalsetImmediaterequestAnimationFrameI/O,用户界面呈现
微任务:process.nextTickPromisesqueueMicrotaskMutationObserver

zzzyeukh

zzzyeukh2#

规范中的基本概念:

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

事件循环流程模型如下:

call stack为空时,执行以下步骤-
1.选择任务队列中最早的任务(任务A)
1.如果任务A为空(意味着任务队列为空),则跳至步骤6
1.将“当前运行任务”设置为“任务A”
1.运行“任务A”(表示运行回调函数)
1.将“当前运行任务”设置为空,删除“任务A”

  • 执行微任务队列
  • (a)选择微任务队列中最早的任务(任务x)
  • (B)如果任务x为空(意味着微任务队列为空),则跳到步骤(g)
  • (c).将“当前运行任务”设置为“任务x”
  • (d).运行“任务x”
  • (e).将“当前运行任务”设置为空,删除“任务x”
  • (f).在微任务队列中选择下一个最早的任务,跳到步骤(B)
  • g.完成微任务排队;
  • 跳到步骤1。

简化工艺模型如下:

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

要记住的事项:

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

plicqrtu3#

我认为我们不能把事件循环从堆栈中分离出来讨论,所以:
JS有三个“堆栈”:

  • 所有同步调用的标准堆栈(一个函数调用另一个函数等)
  • 所有具有较高优先级的异步操作的微任务队列(或作业队列或微任务堆栈)(进程.nextTick、承诺、对象.观察者、突变观察者)
  • 所有具有较低优先级的异步操作的宏任务队列(或事件队列、任务队列、宏任务队列)(设置超时、设置间隔、设置立即、请求动画帧、I/O、UI渲染)
|=======|
| macro |
| [...] |
|       |
|=======|
| micro |
| [...] |
|       |
|=======|
| stack |
| [...] |
|       |
|=======|

事件循环的工作方式如下:

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

如果微堆栈不为空,则不会触及微堆栈。如果微堆栈不为空或不需要任何执行,则不会触及宏堆栈。
总而言之:微任务队列几乎与宏任务队列相同,但这些任务**(进程.nextTick,承诺,对象.观察,突变观察者)**具有比宏任务更高的优先级。

微观与宏观类似,但具有更高的优先级。

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

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

pengsaosao4#

宏任务包括键盘事件、鼠标事件、定时器事件(setTimeout)、网络事件、Html解析、更改Urletc。宏任务代表一些离散的独立工作。微任务队列具有较高的优先级,因此宏任务将等待所有微任务首先执行。
微任务是更新应用程序状态的较小任务,应该在浏览器继续其他任务(如重新呈现UI)之前执行。微任务包括承诺回调和DOM突变更改。微任务使我们能够在重新呈现UI之前执行某些操作,从而避免不必要的UI呈现(可能显示不一致的应用程序状态)。
宏和微任务的分离使得事件循环能够区分任务类型的优先级;例如,优先考虑对性能敏感的任务。
在单循环迭代中,最多处理一个宏任务(其他任务留在队列中等待),而处理所有微任务。

  • 这两个任务队列都放置在事件循环之外,以指示将任务添加到其匹配队列的操作发生在事件循环之外。否则,将忽略执行JavaScript代码时发生的任何事件。检测和添加任务的操作与事件循环分开完成。
  • 这两种类型的任务都是一次执行一个。当一个任务开始执行时,它会一直执行到完成。只有浏览器可以停止任务的执行;如果任务占用了太多的时间或内存,则可能会出现这种情况。
  • 所有微任务都应在下一次渲染之前执行,因为它们的目标是在渲染发生之前更新应用程序状态。

浏览器通常会尝试每秒渲染页面60次,一般认为每秒60帧是动画显示流畅的速率。如果我们希望实现应用程序的流畅运行,则单个任务以及该任务生成的所有微任务应在理想情况下在16 ms内完成。如果任务执行时间超过几秒,浏览器将显示“无响应脚本”消息。
reference John Resig-secrets of JS Ninja

1cosmwyk

1cosmwyk5#

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

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

事件循环的概念非常简单,它是一个无限循环,JavaScript引擎等待任务,执行它们,然后休眠,等待更多的任务
设置任务--引擎处理它们--然后等待更多任务(同时处于睡眠状态,CPU消耗接近于零)。可能会发生这样的情况:任务在引擎忙碌时到来,然后将其入队。任务形成一个队列,即所谓的“macrotask queue

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

vxf3dgd4

vxf3dgd46#

我创建了一个事件循环伪代码,遵循4个概念:
1.setTimeout、setInterval、setImmediate、requestAnimationFrame、I/O、UI呈现是宏任务队列的一部分。将首先处理一个宏任务项。
1.process.nextTick、Promises、Object.observe、MutationObserver是微任务队列的一部分。事件循环将处理该队列中的所有项目,包括在当前迭代期间处理过的一次项目。
1.还有另一个队列称为动画队列,它保存了下一个要处理的动画更改任务项。此队列中存在的所有任务都将被处理(不包括在当前迭代中添加的新任务)。如果到了渲染时间,它将被调用
1.渲染管道将尝试每秒渲染60次(每16毫秒)

while (true){
    // 1. Get one macrotask (oldest) task item
    task = macroTaskQueue.pop(); 
    execute(task);

   // 2. Go and execute microtasks while they have items in their queue (including those which were added during this iteration)
    while (microtaskQueue.hasTasks()){
        const microTask = microtaskQueue.pop();
        execute(microTask);
    }

    // 3. If 16ms have elapsed since last time this condition was true
    if (isPaintTime()){
    // 4. Go and execute animationTasks while they have items in their queue (not including those which were added during this iteration) 
        const animationTasks = animationQueue.getTasks();
        for (task in animationTasks){
            execute(task);
        }

        repaint(); // render the page changes (via the render pipeline)
    }
}

相关问题