文章目录
  1. 1. 示例代码
  2. 2. 宏任务的分类与微任务何时执行
  3. 3. 代码分析

示例代码

浏览器和 Node 都有事件轮询的机制,虽然都属于 JavaScript,但二者的内部机制完全不同。

以下面这段代码为例

setTimeout(()=>{
console.log('timer1')

Promise.resolve().then(function() {
    console.log('promise1')
})
}, 0)

  setTimeout(()=>{
console.log('timer2')

Promise.resolve().then(function() {
    console.log('promise2')
})
}, 0)

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

在浏览器中它的输出顺序是 promise3=>timer1=>promise1=>timer2=>promise2。
而在 Node 中它的输出顺序变为了 promise3=>timer1=>timer2=>promise1=>promise2。

宏任务的分类与微任务何时执行

接下来我们先说明两个概念——宏任务与微任务:

  • macrotask:包含执行整体的js代码,事件回调,XHR回调,定时器(setTimeout/setInterval/setImmediate),IO操作,UI render
  • microtask:更新应用程序状态的任务,包括promise回调,MutationObserver,process.nextTick,Object.observe

浏览器与 Node 事件轮询的不同点就在于宏任务是否归类与微任务何时执行。

在浏览器中,宏任务会按照事件队列中的顺序依次执行,宏任务有可能产生微任务,微任务队列会在当前宏任务执行结束后立即执行

在 Node 中,将宏任务分为六种,如果加上整体的 js 代码一共有七种。具体如下:
图片.png

timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调
I/O callbacks 阶段:执行一些系统调用错误,比如网络通信的错误回调
idle, prepare 阶段:仅node内部使用
poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里
check 阶段:执行 setImmediate() 的回调
close callbacks 阶段:执行 socket 的 close 事件回调

在 Node 中 js 整体代码执行结束后,会将相应的宏任务放到相应的阶段,然后从 timer 阶段开始不断循环执行。每个阶段产生的宏任务会放到其应该属于的阶段,产生的微任务队列会在当前阶段执行结束后立即执行

代码分析

下面我们来逐步分析两个环境的输出为什么不同。

首先可以确定一点,整体 js 代码会先执行,并且从上到下同步执行一遍。在执行的过程中,会碰到异步的代码,即 Promise.then 和 setTimeout ,这两个函数内部的回调函数都不会在这个同步过程中执行。

整体的 js 代码执行后产生了一个微任务 (promise3) 和两个宏任务(timer1、timer2)。

在浏览器中会先执行 promise3 再执行 timer1,执行 timer1 后产生的 promise1 会紧接着 timer1 执行,最后是 timer2 与 promise2。

在 Node 中同样会先执行 promise3 再执行 timer1,但是 timer1 之后会接着执行 timer2,因为这是在 timer 阶段。在 timer1 与 timer2 中产生的两个微任务会在 timer 阶段结束后依次执行。

文章目录
  1. 1. 示例代码
  2. 2. 宏任务的分类与微任务何时执行
  3. 3. 代码分析