PCDotFan

To be an life & code artisan

MacroTasks(宏任务)和 Microtasks(微任务)

JavaScript 0 评

Jake Archibald 版本

Tasks, microtasks, queues and schedules

  • Macrotasks 按序执行,浏览器会在 Macrotasks 之间执行渲染。
  • Microtasks 按序执行,在下面情况时执行:
    1. 在每个回调之后,只要没有其它代码正在运行。
    2. 在每个 Macrotask 的末尾。

何幻版本

[JavaScript] Macrotask Queue 和 Microtask Queue

V8 实现中,两个队列各包含不同的任务:

Macrotasks: script (整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering Microtasks: process.nextTick, Promises, Object.observe, MutationObserver

执行过程如下:

  • JavaScript 引擎首先从 Macrotask queue 中取出第一个任务,
  • 执行完毕后,将 Microtask queue 中的所有任务取出,按顺序全部执行;
  • 然后再从 Macrotask queue 中取下一个,
  • 执行完毕后,再次将 Microtask queue 中的全部取出;
  • 循环往复,直到两个 queue 中的任务都取完。

可以发现,两位大神的版本略有不同,但总体分析的思路是一致的。只是 Jake Archibald 更指出在 JavaScript 栈已空时——Microtasks 同样也会按序执行。(Although we're mid-task, microtasks are processed after callbacks if the stack is empty)

几个小测试

专栏: Promise只能进行异步操作?

专栏: Promise只能进行异步操作?

var promise = new Promise(function (resolve){
    console.log("inner promise"); 
    resolve(42);
});
promise.then(function(value){
    console.log(value); 
});
console.log("outer promise");

结果为:inner promise, outer promise, 42

流程如下(Macrotask 简称 MA;Microtask 简称 MI,下同):

  • (script 整体代码) 添加 MA,此为 MA 队列中第一个任务,开始执行。
    1. 输出 inner promise(Promise 决议前内容立即执行)
    2. resolve(42)
    3. (Promise then) 添加 MI,操作被挂起
    4. 输出 outer promise
  • (script 整体代码) MA 执行完毕,接下来处理刚刚排在队列中的 MI:
    1. 输出 value,决议内容为 42

Tasks, microtasks, queues and schedules

Tasks, microtasks, queues and schedules (Jake 大神网站里的动态示意图真的很有用

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');

结果为:script start, script end, promise1, promise2, setTimeout

流程如下:

  • (script 整体代码) 添加 MA,此为 MA 队列中第一个任务,开始执行。
    1. 输出 script start
    2. (setTimeout) 添加 MA,排在「script 整体代码」的后面,此为 MA 队列中第二个任务,操作被挂起
    3. (Promise then) 添加 MI,操作被挂起(链式调用,then 链节之后的所有操作均被挂起)
  • 输出 script end
  • (script 整体代码) MA 执行完毕,接下来处理刚刚排在队列中的 MI:
    1. 输出 promise1
    2. (Promise then) 添加 MI,操作被挂起
    3. 由于第一个 MA 已经处理完毕,继续处理 MI
    4. 输出 promise2
  • MI 处理完毕,接下来处理 MA 队列中第二个任务,开始执行。
  • 输出 setTimeout

Tasks, microtasks, queues and schedules - Boss Fight

Tasks, microtasks, queues and schedules - Boss Fight 点击一下 inner 部分,console 会输出什么呢?

<div class="outer">
  <div class="inner"></div>
</div>
// Let's get hold of those elements
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');

// Let's listen for attribute changes on the
// outer element
new MutationObserver(function() {
  console.log('mutate');
}).observe(outer, {
  attributes: true
});

// Here's a click listener…
function onClick() {
  console.log('click');

  setTimeout(function() {
    console.log('timeout');
  }, 0);

  Promise.resolve().then(function() {
    console.log('promise');
  });

  outer.setAttribute('data-random', Math.random());
}

// …which we'll attach to both elements
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);

结果为:click, promise, mutate, click, promise, mutate, timeout, timeout

执行流程如下:

  • (处理用户点击事件) 添加 MA(编号 #1),此为 MA 队列中第一个任务,开始执行。
    1. 输出 click
    2. (setTimeout) 添加 MA(编号 #2),操作被挂起
    3. (Promise then) 添加 MI,操作被挂起($1)
    4. setAttribute 操作
    5. setAttribute 操作被 MutationObserver 监听到,添加 MI,MutationObserver 回调操作被挂起($2)
  • MA 操作结束(此时 JS Stack 为空!即在响应下一个 outeronClick 之前无代码可以运行),开始执行队列内的 MI:
    1. 输出 promise
    2. 输出 mutate
  • 事件回调,编号 #1 的 MA 又被重新执行了一次,同 1, 2 步(innerouter 内,先处理 inner 事件后处理 outer 事件)。
    1. (setTimeout) 添加 MA(编号 #3),操作被挂起
  • MA 操作结束,开始执行 #2:输出 timeout
  • MA 操作结束,开始执行 #3:输出 timeout

我很窒息。

Tasks, microtasks, queues and schedules - Boss's angry older brother

还是上面的代码。这次不同的是我们不直接点击,而是在 Console 输入:

inner.click()

会发生什么呐?

结果是:click, click, promise, mutate, promise, timeout, timeout

执行过程如下:

(我真的不想写了,Tasks, microtasks, queues and schedules - Boss's angry older brother

头有点大,:big head:。