# 一. 基础
- 因为
JS是单线程,所以有很多任务的时候就需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。JavaScript 语言的设计者意识到此问题,把所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。 - 事件循环(event loop)
- 同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
- 异步任务分为:分为微任务(Microtasks),宏任务(task);其它线程处理;不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
- 根据规范,事件循环是通过 「任务队列」 的机制来进行协调的。一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),「源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列」。
- 宏任务和微任务执行流程:
- 整段脚本 script 作为宏任务开始执行
- 遇到微任务将其推入微任务队列,宏任务推入宏任务队
- 列宏任务执行完毕,检查有没有可执行的微任务
- 发现有可执行的微任务,将所有微任务执行完毕
- 当前宏任务执行完毕,开始检查渲染,然后 GUI 线程接管渲染
- 渲染完毕后,JS 线程继续接管,开始新的宏任务,反复如此直到所有任务执行完毕
- 主要类型
- 主要宏任务:整段脚本 script、 setTimeout、 setInterval、I/O UI 交互事件、 postMessage、 MessageChannel、 setImmediate(Node.js 环境)
- 主要微任务:promise.then 、catch 、finally、 process.nextTick(Node 使用) 、MutationObserver(浏览器使用)
- 「实际上 await 是一个让出线程的标志」。await 后面的表达式会先执行一遍,将 await 后面的代码加入到 microtask 中,然后就会跳出整个 async 函数来执行后面的代码。(见题三)
- 注意
- 每次执行完一个宏任务后都要去检查微任务
Promise中的异步体现在then和catch中,所以写在Promise中的代码是被当做同步任务立即执行的。而在async/await中,在出现await出现之前,其中的代码也是立即执行的。setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。
# 二. 例子
「题一:」易错
Promise.resolve().then(() => {
console.log("Promise1");
setTimeout(() => {
console.log("setTimeout2");
}, 0);
});
setTimeout(() => {
console.log("setTimeout1");
Promise.resolve().then(() => {
console.log("Promise2");
});
}, 0);
// 输出结果:Promise1,setTimeout1,Promise2,setTimeout2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
从上往下执行,遇到微任务,放入微任务队列;遇到 setTimeout1,放入宏任务队列;输入微任务 Promise1;遇到 setTimeout2,放入宏任务队列;输入宏任务 setTimeout1,Promise2;
解析:
- 一开始执行栈的同步任务执行完毕,会去 microtasks queues 找清空 microtasks queues ,输出 Promise1,同时会生成一个异步任务 setTimeout1
- 去宏任务队列查看此时队列是 setTimeout1 在 setTimeout2 之前,因为 setTimeout1 执行栈一开始的时候就开始异步执行,所以输出 setTimeout1
- 在执行 setTimeout1 时会生成 Promise2 的一个 microtasks ,放入 microtasks queues 中,接着又是一个循环,去清空 microtasks queues ,输出 Promise2
- 清空完 microtasks queues ,就又会去宏任务队列取一个,这回取的是 setTimeout2
「题二:」易错
setTimeout(function() {
console.log("setTimeout");
}, 0);
const p = new Promise((resolve) => {
console.log("a");
resolve();
console.log("b");
});
p.then(() => {
console.log("c");
setTimeout(function() {
console.log("then中的setTimeout");
}, 0);
});
console.log("d");
// 输入结果:a b d c setTimeout then中的setTimeout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
「题三:」易错
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
// 注意async2的输出
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function() {
console.log("setTimeout");
}, 0);
async1();
new Promise(function(resolve) {
console.log("promise1");
resolve();
}).then(function() {
console.log("promise2");
});
console.log("script end");
// 输出: script start async1 start async2 promise1 script end async1 end promise2 setTimeout
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
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
「题四:题三变式」易错
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end"); //注意 async1 end 的输出
}
async function async2() {
//async2做出如下更改:
new Promise(function(resolve) {
console.log("promise1");
resolve();
}).then(function() {
console.log("promise2");
});
}
console.log("script start");
setTimeout(function() {
console.log("setTimeout");
}, 0);
async1();
new Promise(function(resolve) {
console.log("promise3");
resolve();
}).then(function() {
console.log("promise4");
});
console.log("script end");
// 输出:script start async1 start promise1 promise3 script end promise2 async1 end promise4 setTimeout
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
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
「题五:题三变式」易错
async function a1() {
console.log("a1 start");
await a2();
console.log("a1 end");
}
async function a2() {
console.log("a2");
}
console.log("script start");
setTimeout(() => {
console.log("setTimeout");
}, 0);
Promise.resolve().then(() => {
console.log("promise1");
});
a1();
//注意 promise2 也是执行的;并不需要这样promise2(),才执行
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");
// 结果: script start a1 start a2 promise2 script end promise1 a1 end promise2.then promise3 setTimeout
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
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
# 参考
[1] https://juejin.cn/post/6844903752621637645
[2] https://juejin.cn/post/6844903638238756878
[3] 题目: https://juejin.cn/post/6844904202871799821