背景描述

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是一个异步请求函数,我们需要使用asyncawait关键字来等待请求结果返回,但是这样一来,后续链式调用的函数(m1m2m3main)都要加上asyncawait关键字,这种本来没有什么问题,但是如果你处在一个函数式编程环境里面,例如react,所以我们需要消除异步(即去掉asyncawait关键字),直接去掉会导致整个功能都不能用。要解决这个问题呢,要从根源入手。

根源在getUserfetch方法,由于fetch需要等待,导致后续链式调用的函数都需要等待。我们可以把fetch变成一个同步方法,但是这个方法本身是异步的,怎么把它变成异步呢。那就意味着肯定要去改动fetch,它之所以是异步是因为它要进行网络通信,如果把它改成同步,那么网络通信的时间用来干嘛呢?如果用死循环一直等待,页面就会卡死,肯定不可取。

思路解析

无标题-2024-01-16

为了让①中的fetch函数变同步,但是现在网络通信给不了结果,手动抛出异常②。执行栈被终止,按顺序弹出fetchgetUsermain。但是最终我们要的是网络通讯的结果,不能抛出异常,然后就结束了。所以在抛出异常②的同时还需要把请求发出去,即步骤③,请求会经过一段时间,拿到响应结果,我们把请求的结果放入缓存(cache)中,与此同时,再重新启动main函数。也就是说在这种模式下,main函数会调用两次,当执行到fetch函数的时候,发现缓存中有数据,直接拿缓存数据交给getUser,这样就保证了用同步代码的方式来处理异步问题,消除了异步的传染性。

无标题-2024-03-05-2337

进入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会做相关的处理。