背景描述
async function getUser(){
return await fetch("./1.json")
}
async function m1(){
const user = await getUser();
// other works
return user
}
async function m2(){
const user = await m1();
// other works
return user
}
async function m3(){
const user = await m2();
// other works
return user
}
async function main(){
const user = await m3();
console.log(user);
}
先来看下上述代码,getUser
函数中的fetch
是一个异步请求函数,我们需要使用async
和await
关键字来等待请求结果返回,但是这样一来,后续链式调用的函数(m1
、m2
、m3
、main
)都要加上async
和await
关键字,这种本来没有什么问题,但是如果你处在一个函数式编程环境里面,例如react
,所以我们需要消除异步(即去掉async
和await
关键字),直接去掉会导致整个功能都不能用。要解决这个问题呢,要从根源入手。
根源在getUser
中fetch
方法,由于fetch
需要等待,导致后续链式调用的函数都需要等待。我们可以把fetch
变成一个同步方法,但是这个方法本身是异步的,怎么把它变成异步呢。那就意味着肯定要去改动fetch
,它之所以是异步是因为它要进行网络通信,如果把它改成同步,那么网络通信的时间用来干嘛呢?如果用死循环一直等待,页面就会卡死,肯定不可取。
思路解析
为了让①中的fetch
函数变同步,但是现在网络通信给不了结果,手动抛出异常②。执行栈被终止,按顺序弹出fetch
、getUser
、main
。但是最终我们要的是网络通讯的结果,不能抛出异常,然后就结束了。所以在抛出异常②的同时还需要把请求发出去,即步骤③,请求会经过一段时间,拿到响应结果,我们把请求的结果放入缓存(cache)中,与此同时,再重新启动main
函数。也就是说在这种模式下,main
函数会调用两次,当执行到fetch
函数的时候,发现缓存中有数据,直接拿缓存数据交给getUser
,这样就保证了用同步代码的方式来处理异步问题,消除了异步的传染性。
进入fetch
函数之后,先判断有没有缓存,有缓存,返回缓存数据;没有缓存,抛出异常,同时发送请求,拿到请求结果之后设置缓存。
代码实现
function run(func) {
// 1.改动fetch
const oldFetch = window.fetch;
// 注意:如果有多个fetch的情况下,cache需要改成数组,一个cache对象对应一个fetch
const cache = {
status: "pending",
value: null
}
function newFetch(...args){
// 有缓存返回缓存
if (cache.status === "fulfilled"){
return cache.value;
}
else if(cache.status === "rejected"){
throw new cache.value;
}
// 没有缓存,发送请求
// 注意:请求结果有可能不是json类型,需要根据实际请求修改
const p = oldFetch(...args).then((data)=>data.json()).then((data)=>{
cache.value = data;
cache.status = "fulfilled";
}).catch((err)=>{
cache.value = err;
cache.status = "rejected";
})
// 抛出错误
throw p;
}
window.fetch = newFetch;
// 2.执行func
try{
func();
}
catch(err){
// 等待请求完成后重新运行func
// 注意:这里的err类型不一定是ES6的Promise构造函数,可能是手写符合Promise A+规范,最好使用Promise A+规范的方式来判断,需要根据实际请求修改
if (err instanceof Promise){
err.finally(()=>{
window.fetch = newFetch;
func();
window.fetch = oldFetch;
})
}
}
// 3.改回fetch
window.fetch = oldFetch;
}
run(main);
总结
在react
中没有异步组件,在vue
中有,例如vue3
中的setup
函数,如果说返回的是一个Promise
的话,那么vue
会做相关的处理。