Appearance
异步传染
ECMA Script 2016中提出了一个Generator
语法糖async/await
,用于帮助我们书写更清晰的异步代码
要想使用await
获取异步函数结果,必须满足一个特殊的条件:
- 在
async
关键字声明的函数中
JavaScript
async function getUser() {
return await getUserRequest();
}
1
2
3
2
3
如果想要对getUser()
函数再使用await
获取其返回值,也需要修改使用await
的函数为async
函数
JavaScript
async function request1() {
return await getUser();
}
async function request2() {
return await request1();
}
async function request3() {
return await request2();
}
async function request4() {
return await request3();
}
async function main() {
const ans = await request4();
console.log(ans);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
所以我们称async
具有“传染性”
我们想要异步获取一些数据,但不是所有的情况下我们都希望使用async
声明函数,原因也是async
具有传染性,一处使用async
所有相关调用都要使用async
,破坏了原函数的同步特性,大有牵一发而动全身的意思
此时我们希望的调用方式是这样:
JavaScript
function getName() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 3000, "luowei");
});
}
function main() {
const name = getName();
console.log(name);
// luowei
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
这里我将引用React 技术揭秘中的虚构语法做演示
虚构一个try...handle
语法和两个操作符perform
和resume
JavaScript
function getName() {
return perform new Promise((resolve, reject) => {
setTimeout(resolve, 3000, "luowei");
})
}
function main() {
const name = getName();
console.log(name);
// luowei
}
try{
main();
} handle(value) {
if(value instanceof Promise){
value.then(name => {
resume with name;
})
}else{
resume with value;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
perform
操作符行为与throw
相似
handle
操作符与catch
相似
最大的区别在于resume
操作符,它可以无视回调嵌套返回异步结果,也就是说:
JavaScript
if(value instanceof Promise){
value.then(name => {
resume with name;
})
}
1
2
3
4
5
2
3
4
5
这里resume with name
后会将值回传main
函数中继续执行,resume with name
语句会将name
回传给perform
返回
通过这样一个虚构语法我们能够实现同步代码中获取异步返回值的过程
try...catch
与try...handle
语法相似
JavaScript
function getName() {
return throw new Promise((resolve, reject) => {
setTimeout(resolve, 3000, "luowei");
})
}
function main() {
const name = getName();
console.log(name);
}
try{
main();
} catch(value) {
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
使用try...catch
与使用try...handle
的最大区别在于——函数执行上下文是否被销毁
try...catch
:
main
函数中抛出错误->被catch
捕获
此时执行main
的函数执行上下文已经被弹出执行上下文栈
try...handle
:
main
函数中perform
一个值->被handle
获取->处理结束后返回给perform
操作符->继续执行main
函数剩余部分
此时在handle
获取期间,main
的函数执行上下文仍被保留在执行上下文栈中
总结下来,如果能够让try...catch
中的执行上下文得到保留,则近似得到了try...handle
的效果
经过分析,在函数执行上下文的词法环境和变量环境中,存在两种类型的值
- 同步返回的值
- 异步返回的值
保存函数执行上下文当然是不现实的,但如果将异步返回的值全部转变为同步返回的值,再重新执行一次main
函数,是否也能够得到类似的效果?
为此我们需要一个cache
数组,来缓存异步返回的值,在后续调用时优先从cache
数组中读取内容
- 创建
run
函数,用于执行main
函数和构建缓存
JavaScript
function getName() {
return throw new Promise((resolve, reject) => {
setTimeout(resolve, 3000, "luowei");
})
}
function main() {
const name = getName();
console.log(name);
}
function run() {
const cache = [];
let index = 0;
try {
main();
} catch (error) {
}
}
run();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 我们需要对异步返回的值做一点修改,具体来讲是重写异步返回值的获取函数
策略如下:
- 判断是否存在缓存,如果命中缓存则直接返回
- 如果没有命中缓存,则抛出一个
promise
,并将promise
的返回值存入缓存
JavaScript
const _originGetName = getName;
getName = () => {
// 判断是否已经存在缓存值
if (cache[index] && cache[index].status === "fulfilled") {
return [cache[index].data, cache[index].err];
}
if (cache[index] && cache[index].status === "rejected") {
return [cache[index].data, cache[index].err];
}
// 构建缓存值
const promiseResult = {
status: "pending",
data: null,
err: null,
};
// 存入cache数组
cache[index++] = promiseResult;
// 抛出源getName().then(),在其中修改promiseResult状态
throw _originGetName().then(
value => {
promiseResult.status = "fulfilled";
promiseResult.data = value;
},
reason => {
promiseResult.status = "rejected";
promiseResult.data = reason;
}
);
};
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
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
- 在
catch
中判断是否是promise
类型,如果是,在promise
兑现后重新执行main
函数
JavaScript
try {
main();
} catch (error) {
if (error instanceof Promise) {
error.finally(() => {
// 下标重置为0
index = 0;
main();
});
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
完整代码如下:
JavaScript
function run() {
const cache = [];
let index = 0;
const _originGetName = getName;
getName = () => {
if (cache[index] && cache[index].status === "fulfilled") {
return [cache[index].data, cache[index].err];
}
if (cache[index] && cache[index].status === "rejected") {
return [cache[index].data, cache[index].err];
}
const promiseResult = {
status: "pending",
data: null,
err: null,
};
cache[index++] = promiseResult;
throw _originGetName("luowei").then(
value => {
promiseResult.status = "fulfilled";
promiseResult.data = value;
},
reason => {
promiseResult.status = "rejected";
promiseResult.data = reason;
}
);
};
try {
main();
} catch (error) {
if (error instanceof Promise) {
error.finally(() => {
index = 0;
main();
});
}
}
}
run();
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
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
执行流程图:
类似
TypeScript
const name = getName()
1
这种,我们不关心getName()
实现,只在乎获取到name
并返回结果的过程叫做代数效应
React Hooks
就是代数效应的最佳实践
TypeScript
const [num, updateNum] = useState(0);
// 只需要假设useState返回的是我们想要的state
1
2
2
更加明显的体现是Suspense
组件
Suspense`组件会在子组件挂起状态时渲染`fallback component
1
使用lazy
进行懒加载的组件就是返回一个promise
,当promise
状态为pending
时渲染fallback
,promise
兑现后渲染对应组件