事件循环:微任务和宏任务(二)

1,112 阅读3分钟

宏任务和微任务

除了本章中所讲的 宏任务(macrotask)  外,还有在 微任务(Microtask) 一章中提到的 微任务(microtask)

微任务仅来自于我们的代码。它们通常是由 promise 创建的:对 .then/catch/finally 处理程序的执行会成为微任务。微任务也被用于 await 的“幕后”,因为它是 promise 处理的另一种形式。

还有一个特殊的函数 queueMicrotask(func),它对 func 进行排队,以在微任务队列中执行。

每个宏任务之后,引擎会立即执行微任务队列中的所有任务,然后再执行其他的宏任务,或渲染,或进行其他任何操作。

例如,看看下面这个示例:

setTimeout(() => alert("timeout"));

Promise.resolve()
  .then(() => alert("promise"));

alert("code");

这里的执行顺序是怎样的?

  1. code 首先显示,因为它是常规的同步调用。
  2. promise 第二个出现,因为 then 会通过微任务队列,并在当前代码之后执行。
  3. timeout 最后显示,因为它是一个宏任务。

更详细的事件循环图示如下(顺序是从上到下,即:首先是脚本,然后是微任务,渲染等):

1640329689(1).jpg 微任务会在执行任何其他事件处理,或渲染,或执行任何其他宏任务之前完成。

这很重要,因为它确保了微任务之间的应用程序环境基本相同(没有鼠标坐标更改,没有新的网络数据等)。

如果我们想要异步执行(在当前代码之后)一个函数,但是要在更改被渲染或新事件被处理之前执行,那么我们可以使用 queueMicrotask 来对其进行安排(schedule)。

这是一个与前面那个例子类似的,带有“计数进度条”的示例,但是它使用了 queueMicrotask 而不是 setTimeout。你可以看到它在最后才渲染。就像写的是同步代码一样:

<div id="progress"></div>

<script>
  let i = 0;

  function count() {

    // 做繁重的任务的一部分 (*)
    do {
      i++;
      progress.innerHTML = i;
    } while (i % 1e3 != 0);

    if (i < 1e6) {
queueMicrotask(count);
    }

  }

  count();
</script>

总结

更详细的事件循环算法(尽管与规范) 相比仍然是简化过的):

  1. 从 宏任务 队列(例如 “script”)中出队(dequeue)并执行最早的任务。

  2. 执行所有 微任务

    • 当微任务队列非空时:

      • 出队(dequeue)并执行最早的微任务。
  3. 如果有变更,则将变更渲染出来。

  4. 如果宏任务队列为空,则休眠直到出现宏任务。

  5. 转到步骤 1。

安排(schedule)一个新的 宏任务

  • 使用零延迟的 setTimeout(f)

它可被用于将繁重的计算任务拆分成多个部分,以使浏览器能够对用户事件作出反应,并在任务的各部分之间显示任务进度。

此外,也被用于在事件处理程序中,将一个行为(action)安排(schedule)在事件被完全处理(冒泡完成)后。

安排一个新的 微任务

  • 使用 queueMicrotask(f)
  • promise 处理程序也会通过微任务队列。

在微任务之间没有 UI 或网络事件的处理:它们一个立即接一个地执行。

所以,我们可以使用 queueMicrotask 来在保持环境状态一致的情况下,异步地执行一个函数。

Web Workers

对于不应该阻塞事件循环的耗时长的繁重计算任务,我们可以使用 Web Workers

这是在另一个并行线程中运行代码的方式。

Web Workers 可以与主线程交换消息,但是它们具有自己的变量和事件循环。

Web Workers 没有访问 DOM 的权限,因此,它们对于同时使用多个 CPU 内核的计算非常有用。


转载详情:

转载网站: Javascript.info

转载地址:zh.javascript.info/event-loop