eventLoop

Event Loop题目解析

首先根据一次弄懂Event Loop我们知道chrome73以前版本与后面版本,以及nodejs对eventLoop的表现行为是有区别的。我们这里仅仅讨论 >=chrome73的情况

在chrome中测试下面的用例时,函数的返回值,不算在输出中

题目

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
33
34
35
36
37
38
39
40
41
42
async function a1 () {
console.log('a1 start')
await a2()
console.log('a1 end')
}
async function a2 () {
console.log('a2')
}

async function a3() {
console.log('a3')
}

console.log('script start')

setTimeout(async () => {
console.log('setTimeout')
await a3()
console.log('a3-end')
}, 0)

Promise.resolve().then(() => {
console.log('promise1')
})

a1()

let promise2 = new Promise((resolve) => {
resolve('promise2.then')
console.log('promise2')
})

promise2.then((res) => {
console.log(res)
Promise.resolve().then(() => {
console.log('promise3')
})
})
console.log('script end')
setTimeout(() => {
console.log('second setTimeout')
}, 1)

这道写输出结果的题目关键点在于setTimeout, async/await, Promise/Promise.resolve, then

题目解析

首先我们要知道微任务与宏任务的概念。

任务分类

宏任务包括:script全部代码、setTimeout、setInterval、setImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN)、I/O、UI Rendering。

微任务包括:Process.nextTick(Node独有)、Promise、Object.observe(废弃)、MutationObserver

执行顺序

同步任务执行完后,检查微任务队列是否为空,如果不为空,就将微任务队列以先进先出的顺序全部执行并出队。如果为空就检查宏任务队列,如果不为空,就执行一个宏任务并出队,然后再次检查微任务队列(重复上文微任务检查执行方式),如此循环。

关键点分析

Promise/then

1
2
3
4
5
6
7
8
let pro = new Promise((resolve) => {
resolve('pro')
})
pro.then(val => {
console.log(val);
})
console.log('pro1');
// pro1 -> pro
  1. 首先执行同步任务,输出pro1
  2. 此时同步任务执行完毕,检查微任务队列不为空,所以执行微任务队列输出pro

setTimeout, Promise/then

1
2
3
4
5
6
7
8
9
setTimeout(() => {
console.log('setTimeout');
}, 0);
console.log('normal');
let pro = Promise.resolve()
.then(() => {
console.log('resolve')
})
// normal -> resolve -> setTimeout
  1. 首先执行同步任务输出normal
  2. 此时同步任务执行完毕,检查微任务队列不为空,所以执行微任务队列输出resolve
  3. 微任务队列为空,检查宏任务队列不为空,执行宏任务输出setTimeout

Promise, async/await

1
2
3
4
5
6
7
8
9
10
11
12
async function a() {
console.log('a');
}
async function b() {
console.log('b');
await a();
console.log('b-end');
}
b();
console.log('normal');

// b -> a -> normal -> b-end

因为async/await 是Promise的语法糖,所以先把代码简化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function a() {
console.log('a');
}
function b() {
console.log('b');
let pro = new Promise((resolve) => {
a();
resolve();
})
pro.then(() => {
console.log('b-end');
})
}
b();
console.log('normal')

在async函数中使用await,相当与将await下面的代码放在then中。
await fun(), fun放在new Promise(resolve => { …这里 })

题目解答

  1. 首先执行同步任务输出:

script start -> a1 start -> a2 -> promise2 -> script end

  1. 接下来查看微任务队列, 发现不为空,然后顺序执行微任务队列。输出:

promise1 -> a1 end -> promise2.then

  1. 由于在执行微任务队列的最后一个任务时,又向微任务队列中增加了一个任务,所以执行完最后一个微任务后,微任务队列不为空,继续按顺序执行。输出

promise3

  1. 微任务队列为空,查看宏任务队列不为空,按顺序执行一个宏任务,输出

setTimeout -> a3

  1. 一个宏任务执行完,同时又向微任务队列增加了一个任务,此时微任务队列不为空,所以按顺序将微任务队列的任务执行完,输出

a3-end

  1. 微任务队列执行完,检查宏任务队列不为空,再继续执行一个宏任务,输出

second setTimeout

参考文章

一次弄懂Event Loop
前端面试总结