JS 异步

异步

JS 是单线程语言,同一时间只能做一件事儿,并且 JS 执行和 DOM 渲染共用一个线程;

JS 异步采用单线程非阻塞式方法实现,基于 event loop 机制;

异步和同步的区别

  1. 异步不会阻塞代码的执行,想象一下 console;

    异步

  2. 同步会阻塞代码的执行,想象一下 alert;

    同步

前端使用异步的场景

  1. 网络请求,如加载图片;
  2. 定时任务,如 setTimeout;

描述 event loop 的机制(事件循环/事件轮询)

异步使用回调,基于 event loop;

DOM 事件也使用回调,同样基于 event loop;

  1. 同步代码一行一行依次执行;
  2. 遇到异步代码则记录下来;
  3. 同步代码执行完毕后,启动事件轮询机制;
  4. 获取当前可执行的异步代码并执行;

微任务和宏任务分别有哪些,有什么区别?

  1. 宏任务:setTimeout、setInterval、ajax、DOM 事件等;
  2. 微任务:Promise、async、await 等;
  3. 微任务的执行时机比宏任务要早;
  4. 微任务在 DOM 渲染前触发;
  5. 宏任务在 DOM 渲染后触发;

event loop 和 DOM 渲染的关系

  1. 调用栈空闲时,也就是同步代码执行完毕后;
  2. 先执行可执行的微任务;
  3. 然后尝试触发 DOM 渲染;
  4. 接着执行可执行的宏任务;
  5. 循环往复执行 2~4 步;

promise 的三种状态

  1. 只能由 pending 变为 resolved(fulfilled);
  2. 或由 pending 变为 rejected;
  3. 且变化不可逆;

then 和 catch 对状态的影响(重要)

  1. then 在内部代码执行完毕后,返回 resolved 状态的 promise,代码执行出错则返回 rejected 状态的 promise;
  2. catch 在内部代码执行完毕后,返回 resolved 状态的 promise,代码执行出错则返回 rejected 状态的 promise;
  3. then 和 catch 都可以触发 then 回调,也都有可能触发 catch 回调;

用几道题目来理解上面的内容:

第一题

1
2
3
4
5
6
7
8
9
10
11
12
Promise.resolve() // 状态 resolved,触发 then
.then(() => {
console.log(1);
}) // 状态 resolved,触发 then,跳过 catch
.catch(() => {
console.log(2);
}) // 不执行
.then(() => {
console.log(3);
}); // 状态 resolved,结束

// 最终输出 1 3

then 和 catch 就像 switch 中的 case 一样,不会因为位置的变化被阻断,而是会依次匹配符合当前状态的选项;

第二题

1
2
3
4
5
6
7
8
9
10
11
12
13
Promise.resolve() // 状态 resolved,触发 then
.then(() => {
console.log(1);
throw new Error("then error");
}) // then 中出错,状态 rejected,触发 catch
.catch(() => {
console.log(2);
}) // 状态 resolved,触发 then
.then(() => {
console.log(3);
}); // 状态 resolved,结束

// 最终输出 1 2 3

then 会触发 catch,catch 也可以触发 then,匹配结果取决于内部代码的执行状态,而不是 then 和 catch 本身的语义;

第三题

1
2
3
4
5
6
7
8
9
10
11
12
13
Promise.resolve() // 状态 resolved,触发 then
.then(() => {
console.log(1);
throw new Error("then error");
}) // then 中出错,状态 rejected,触发 catch
.catch(() => {
console.log(2);
}) // 状态 resolved,没有匹配项,结束
.catch(() => {
console.log(3);
}); // 跳过

// 最终输出 1 2

catch 中的代码执行没有产生错误,返回的也是 resolved 状态的 promise;

什么是 async / await

用同步的语法编写异步的代码,彻底消灭了回调,但本质上还是异步;

1
2
3
4
5
6
7
8
9
10
11
12
13
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end"); // 关键在这一步,它相当于放在 callback 中,最后执行
}

async function async2() {
console.log("async2");
}

console.log("script start");
async1();
console.log("script end");

执行结果如下:

async

  1. await 必须在 async 函数中使用;

  2. await 后面的代码可以认为是 callback 中的代码,await 不返回,后面的代码不执行;

    await 后面跟 promise 对象时,执行与否取决于 promise 对象的状态,如下:

    1
    2
    3
    4
    5
    (async function () {
    const p = new Promise(() => {}); // 没有 resolve 和 reject,所以是 pending 状态
    await p; // 阻断后面代码
    console.log("p"); // 不执行
    })();

    await 后面跟非 promise 对象时,直接返回,如下:

    1
    2
    3
    4
    (async function () {
    const res = await 100;
    console.log(res); // 100
    })();

    await 只能处理 resolved 状态的 promise,如下:

    1
    2
    3
    4
    5
    (async function () {
    const p = Promise.reject("some error"); // 状态 rejected
    const res = await p; // await 无法处理
    console.log(res); // 不执行
    })();

async / await 和 promise 的关系

  1. 执行 async 函数返回的是 promise 对象;
  2. await 相当于 promise 的 then;
  3. await 无法处理 rejected 状态的 promise;
  4. try…catch 可以捕获异常,代替 promise 的 catch;

经典面试题

复习检查一下自己是否完全理解了;

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"); // 同步代码 - 2
await async2();
console.log("async1 end"); // 微任务 - 6
}

async function async2() {
console.log("async2"); // 同步代码 - 3
}

console.log("script start"); // 同步代码 - 1

setTimeout(function () {
console.log("setTimeout"); // 宏任务 - 8
}, 0);

async1();

new Promise(function (resolve) {
console.log("promise1"); // 同步代码 - 4
resolve();
}).then(function () {
console.log("promise2"); // 微任务 - 7
});

console.log("script end"); // 同步代码 - 5

执行结果如下:

经典

for…of

  1. for…of 遍历是异步循环;

    fo...of

  2. 其他遍历是同步循环,如 for…in、forEach 等;

    同步循环

手写 promise

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
class MyPromise {
state = "pending"; // 状态, pending fulfilled rejected
value = undefined; // fulfilled 的值
reason = undefined; // rejected 的信息

// pending 状态下,存储 then 中的回调函数
resolveCallbacks = [];
rejectCallbacks = [];

// 众所周知,promise 接收一个函数作为参数
constructor(fn) {
// 使用 resolve 时,将状态更改为 fulfilled,并存储传入的参数供 then 使用
// 同时,遍历并执行 pending 状态下存储的回调函数
const resolveHandler = (value) => {
if (this.state === "pending") {
this.state = "fulfilled";
this.value = value;
this.resolveCallbacks.forEach((fn) => fn(this.value));
}
};
// 使用 reject 时,将状态更改为 rejected,并存储传入的参数供 catch 使用
// 同时,遍历并执行 pending 状态下存储的回调函数
const rejectHandler = (reason) => {
if (this.state === "pending") {
this.state = "rejected";
this.reason = reason;
this.rejectCallbacks.forEach((fn) => fn(this.reason));
}
};

try {
fn(resolveHandler, rejectHandler);
} catch (error) {
rejectHandler(error);
}
}

then(fn1, fn2) {
// 判断传入的是否是函数
fn1 = typeof fn1 === "function" ? fn1 : (v) => v;
fn2 = typeof fn2 === "function" ? fn2 : (e) => e;

// pending 状态下(异步代码),将回调函数保存,等待状态变更时执行
if (this.state === "pending") {
return new MyPromise((resolve, reject) => {
this.resolveCallbacks.push(() => {
try {
const newValue = fn1(this.value);
resolve(newValue);
} catch (error) {
reject(error);
}
});
this.rejectCallbacks.push(() => {
try {
const newReason = fn2(this.reason);
reject(newReason);
} catch (error) {
reject(error);
}
});
});
}

// promise 的状态为 fulfilled,直接执行回调函数
if (this.state === "fulfilled") {
return new MyPromise((resolve, reject) => {
try {
const newValue = fn1(this.value);
resolve(newValue);
} catch (error) {
reject(error);
}
});
}
// promise 的状态为 rejected,直接执行回调函数
if (this.state === "rejected") {
return new MyPromise((resolve, reject) => {
try {
const newReason = fn2(this.reason);
reject(this.reason);
} catch (error) {
reject(error);
}
});
}
}

catch(fn) {
return this.then(null, fn);
}
}

MyPromise.resolve = (value) => {
return new MyPromise((resolve, reject) => resolve(value));
};
MyPromise.reject = (reason) => {
return new MyPromise((resolve, reject) => reject(reason));
};
MyPromise.all = (promiseList = []) => {
return new MyPromise((resolve, reject) => {
const result = []; // 存储所有结果
const length = promiseList.length; // 获取 promise 的数量
let resolved = 0;
计数器;

promiseList.forEach((p) => {
try {
p.then((data) => {
result.push(data);
// forEach 是同步遍历,会在一瞬间遍历完成,所以需要在 then 中统计 promise 的执行结果
resolved++;
if (resolved === length) {
resolve(result);
}
});
} catch (error) {
reject(error);
}
});
});
};
MyPromise.race = (promiseList = []) => {
return new MyPromise((resolve, reject) => {
let resolved = false; // 标识
promiseList.forEach((p) => {
try {
p.then((data) => {
if (!resolved) {
// 有一个成功直接返回结果,不再继续
resolve(data);
resolved = true;
}
});
} catch (error) {
reject(error);
}
});
});
};