Microtasks 与 Macrotasks

作为一个【实际问题驱动学习】的前端萌新,每次学习动力的激发都来自于某个问题:

console.log('start')

const interval = setInterval(() => {  
  console.log('setInterval')
}, 0)

setTimeout(() => {  
  console.log('setTimeout 1')
  Promise.resolve()
      .then(() => {
        console.log('promise 3')
      })
      .then(() => {
        console.log('promise 4')
      })
      .then(() => {
        setTimeout(() => {
          console.log('setTimeout 2')
          Promise.resolve()
              .then(() => {
                console.log('promise 5')
              })
              .then(() => {
                console.log('promise 6')
              })
              .then(() => {
                clearInterval(interval)
              })
        }, 0)
      })
}, 0)

Promise.resolve()
    .then(() => {  
        console.log('promise 1')
    })
    .then(() => {
        console.log('promise 2')
    })

当然,这是一道为了考察知识而特意出的题目,其实并不算实际问题,而真正让我想要继续了解 js 事件机制的原因是最近对 Promise 的新体会:Promise 之所以无法使用 catch 捕获 setTimeout 回调中的错误,是因为 Promise 的 then/catch 是在 setTimeout 之前执行的。

同为异步事件,为何有先后之分呢?

因为它们是不同的异步事件类型:Microtasks 与 Macrotasks

众所周知

js 的 event loop 如下:


Microtasks 与 Macrotasks_第1张图片
image.png

主线程会先执行同步代码,遇到异步代码则将其插入异步“任务队列”。待主线程空了,再会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。

可是

关键在于“任务队列”不止一个,它分为 Microtasks queue 与 Macrotasks queue,分别存放着 Microtasks 与 Macrotasks:

Microtasks:
  • process.nextTick
  • promise
  • Object.observe
  • MutationObserver
Macrotasks:
  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI渲染

而且,一个 event loop (事件循环) 会包含多个 Macrotasks queue,也就是我们常说的 task queue (事件队列),但只包含一个 Microtasks queue,异步事件会根据自己的事件类型分别放入不同的 queue。所以有

1. task queue == macrotask queue != microtask queue
2. event loop = {
  macrotask queue list: [macrotask queue0, macrotask queue1, ...],
  microtask queue: ...,
}

理解了这些定义之后,再看执行原理:

事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的microtasks。当所有可执行的microtasks执行完毕之后。循环再次从macrotasks开始,找到其中一个任务队列执行完毕,然后再执行所有的microtasks,这样一直循环下去。

翻译过来就是,先执行 Microtasks queue 中的所有 Microtasks,再挑一个 Macrotasks queue 来执行其中所有 Macrotasks,然后继续执行 Microtasks queue 中的所有 Microtasks,再挑一个 Macrotasks queue 来执行其中所有 Macrotasks ……

这也就解释了,为什么同一个事件循环中的 Microtasks 会比 Macrotasks 先执行。

还要注意一点:

包裹在一个 script 标签中的 js 代码也是一个 Macrotasks

答案

start 
promise 1 
promise 2 
setInterval 
setTimeout 1 
promise 3 
promise 4 
setInterval 
setTimeout 2 
promise 5 
promise 6

你可能感兴趣的:(Microtasks 与 Macrotasks)