# 一. 基础

  1. 因为JS是单线程,所以有很多任务的时候就需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。JavaScript 语言的设计者意识到此问题,把所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)
  2. 事件循环(event loop)
  • 同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
  • 异步任务分为:分为微任务(Microtasks),宏任务(task);其它线程处理;不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
  • 根据规范,事件循环是通过 「任务队列」 的机制来进行协调的。一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),「源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列」。
  1. 宏任务和微任务执行流程:
  • 整段脚本 script 作为宏任务开始执行
  • 遇到微任务将其推入微任务队列,宏任务推入宏任务队
  • 列宏任务执行完毕,检查有没有可执行的微任务
  • 发现有可执行的微任务,将所有微任务执行完毕
  • 当前宏任务执行完毕,开始检查渲染,然后 GUI 线程接管渲染
  • 渲染完毕后,JS 线程继续接管,开始新的宏任务,反复如此直到所有任务执行完毕
  1. 主要类型
  • 主要宏任务:整段脚本 script、 setTimeout、 setInterval、I/O UI 交互事件、 postMessage、 MessageChannel、 setImmediate(Node.js 环境)
  • 主要微任务:promise.then 、catch 、finally、 process.nextTick(Node 使用) 、MutationObserver(浏览器使用)
  • 「实际上 await 是一个让出线程的标志」。await 后面的表达式会先执行一遍,将 await 后面的代码加入到 microtask 中,然后就会跳出整个 async 函数来执行后面的代码。(见题三)
  1. 注意
  • 每次执行完一个宏任务后都要去检查微任务
  • Promise中的异步体现在thencatch中,所以写在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

从上往下执行,遇到微任务,放入微任务队列;遇到 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

「题三:」易错

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

「题四:题三变式」易错

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

「题五:题三变式」易错

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

# 参考

[1] https://juejin.cn/post/6844903752621637645

[2] https://juejin.cn/post/6844903638238756878

[3] 题目: https://juejin.cn/post/6844904202871799821