nodejs 中的异步执行顺序

前段时间给朋友讲解 Node.js 的异步执行问题,今天翻到当时的代码和解释,做个记录

什么是事件循环?

在讲解具体代码之前,我们先了解一下什么是事件循环(Event Loop)。

可以把事件循环想象成一个游乐场的管理员,他管理着两个队伍:

  1. 宏任务队列(Macrotask Queue):就像游乐场里的大型项目队伍,比如过山车、摩天轮等,需要较长时间体验
  2. 微任务队列(Microtask Queue):就像游乐场里的小型项目队伍,比如旋转木马、碰碰车等,可以快速体验

事件循环的工作方式是:

  1. 检查微任务队列,把所有的小项目(微任务)都玩一遍
  2. 然后从大项目(宏任务)队伍中选择第一个项目去体验
  3. 体验完这个大项目后,再次检查并清空所有小项目队伍
  4. 重复这个过程,直到所有项目都体验完毕

代码示例

让我们通过一个具体例子来理解这个过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 定义一个简化版的 setTimeout
let timeout = cb => setTimeout(cb, 0)

// 定义一个返回 Promise 的函数
let f = flag => new Promise(resolve => {
console.log('start', flag)
resolve(flag)
}).then(flag => {
console.log('p-' + flag)
// 在微任务中注册一个新的宏任务
setTimeout(function __inner__() {
console.log('inner ' + flag)
})
}).then(() => {
console.log('p-debug')
})

// 注册宏任务
timeout(function __hello__() {
console.log('hello')
// 调用函数 f,产生微任务
f(3)
})

// 注册宏任务
timeout(function __world__() {
console.log('world')
})

// 直接调用函数 f,产生微任务
f(1)
f(2)

执行过程详解

第一阶段:同步代码执行

首先执行所有同步代码,按顺序注册宏任务和微任务:

第 27 行,注册 hello 宏任务

1
2
宏任务队列:[__hello__]
微任务队列:[]

第 33 行注册 world 宏任务

1
2
宏任务队列:[__hello____world__]
微任务队列:[]

第 37 行调用 Promise,输出 “start 1”,并注册微任务 then1.1

1
2
3
控制台输出:start 1
宏任务队列:[__hello____world__]
微任务队列:[then1.1]

第 38 行调用 Promise,输出 “start 2”,并注册微任务 then2.1

1
2
3
控制台输出:start 2
宏任务队列:[__hello____world__]
微任务队列:[then1.1,then2.1]

同步代码执行完毕,接下来进入事件循环阶段。

第二阶段:执行微任务队列

执行所有微任务,按顺序处理:

  1. 执行 then1.1 微任务

    1
    2
    3
    4
    控制台输出:p-1
    注册宏任务:__inner 1__
    宏任务队列:[__hello____world____inner 1__]
    微任务队列:[]
  2. 执行 then2.1 微任务

    1
    2
    3
    4
    控制台输出:p-2
    注册宏任务:__inner 2__
    宏任务队列:[__hello____world____inner 1__,__inner 2__]
    微任务队列:[then1.2] // then1.1 执行完后注册的 then1.2
  3. 执行 then1.2 微任务

    1
    2
    3
    控制台输出:p-debug
    宏任务队列:[__hello____world____inner 1__,__inner 2__]
    微任务队列:[then2.2] // then2.1 执行完后注册的 then2.2
  4. 执行 then2.2 微任务

    1
    2
    3
    控制台输出:p-debug
    宏任务队列:[__hello____world____inner 1____inner 2__]
    微任务队列:[]

微任务队列清空,准备执行第一个宏任务。

第三阶段:执行宏任务和相应的微任务

  1. 执行 hello 宏任务

    1
    2
    3
    4
    5
    6
    控制台输出:hello
    调用 f(3):
    控制台输出:start 3
    注册微任务:then3.1
    宏任务队列:[__world__,__inner 1__,__inner 2__]
    微任务队列:[then3.1]
  2. 执行 then3.1 微任务

    1
    2
    3
    4
    控制台输出:p-3
    注册宏任务:__inner 3__
    宏任务队列:[__world____inner 1____inner 2____inner 3__]
    微任务队列:[]
  3. 执行 then3.1 后续的微任务

    1
    2
    3
    控制台输出:p-debug
    宏任务队列:[__world____inner 1____inner 2____inner 3__]
    微任务队列:[]

第四阶段:依次执行剩余宏任务

  1. 执行 world 宏任务

    1
    2
    3
    控制台输出:world
    宏任务队列:[__inner 1____inner 2____inner 3__]
    微任务队列:[]
  2. 执行 inner 1 宏任务

    1
    2
    3
    控制台输出:inner 1
    宏任务队列:[__inner 2____inner 3__]
    微任务队列:[]
  3. 执行 inner 2 宏任务

    1
    2
    3
    控制台输出:inner 2
    宏任务队列:[__inner 3__]
    微任务队列:[]
  4. 执行 inner 3 宏任务

    1
    2
    3
    控制台输出:inner 3
    宏任务队列:[]
    微任务队列:[]

至此,宏任务和微任务队列全部清空,程序执行完毕。


nodejs 中的异步执行顺序
https://bubao.github.io/posts/f05aaded.html
作者
一念
发布于
2021年11月18日
许可协议