消除async/await异步的传染性

2023-11-04  本文已影响0人  猪猪行天下

我们看下下面函数的调用:

async function getUser() {
    return await fetch('./1.json');
}

async function m1() {
    const user = await getUser();
    return user;
}

async function m2() {
    const user = await m1();
    return user;
}
async function m3() {
    const user = await m2();
    return user;
}

async function main() {
    const user = await m3();
    console.log(user);
}

从上面的函数调用可以看出来,getUser是异步函数,所有使用和相关联的函数都必须使用async/await变成异步函数,这样使用也没有什么问题,但是在函数式编程环境中就不合适了。
本来这些函数应该是一个纯函数的,却因为异步具有传染性,导致这些函数全部变成副作用的了,这在函数式编程环境中是很难接受的。

所以如何不去改动这些函数,把这些异步全部去掉呢?变成没有异步的样子,从而保持这些函数的纯度。如下:

 function getUser() {
    return fetch('./1.json');
}

 function m1() {
    const user = getUser();
    return user;
}

 function m2() {
    const user = m1();
    return user;
}
 function m3() {
    const user = m2();
    return user;
}

 function main() {
    const user = m3();
    console.log(user);
}

怎么操作呢?getUser调用了fetch请求,导致了异步的产生。
网络传输是需要时间的,这个是无法改变的。让浏览器完全阻塞那就卡死了,肯定是行不通的。
目前的函数调用流程如下:

fetch-1.png

main->getUser->fetch - -> 等待网络请求,请求完成 --> getUser->main

由于fetch需要等待导致所有相关的函数都要等待。那么只能在fetch这里做一些操作了。如何让fetch不等待,就只能报错了。

fetch-2.png

我们看下通过fetch报错如何解决这个问题。

main->getUser->fetch->error
拿到结果存入cache: main->getUser->fetch->cache->getUser->main

在调用fetch的时候不等待了而是报错,这样所有函数都终止了,调用栈层层弹出,调用结束。

但是我们最终的目的是要拿到结果的,前面虽然报错了,网络线程仍然还在继续网络请求它不会停止,直到拿到结果。

拿到结果后我们把它放在一个缓存中,接着再去恢复整个调用链的执行。再执行fetch时,结果已经缓存在cache了,取出数据就可以直接交付不用等待了从而变成了同步函数。

整个过程会走两次,第一次以错误结束,第二次以成功结束,这两次都是同步的。

在这个过程中fetch的逻辑就发生了变化:
fetch时要判断是否有缓存,如果有缓存则返回缓存,如果没有缓存则发送真实请求同时抛出错误,然后把请求的结果保存。抛出的错误为发送请求返回的Promise对象,目的是为了在请求完成后再去恢复调用。

伪代码实现如下:

function run(func) {
    let cache = {
        status: 'pending',
        value: null
    };
    const oldFetch = window.fetch;
    window.fetch = function(...args){
        if(cache.status == 'fulfilled'){
            return cache.value;
        }else if(cache.status == 'rejected'){
            //之前的请求有问题
            throw cache.value;
        }else{
            // 1. 发送真是请求
            const promise = oldFetch(...args)
            .then(res=>{
                cache.status = 'fulfilled';
                cache.value = res;
            }, err=> {
                cache.status = 'rejected';
                cache.value = err;
            });
            // 2. 抛出错误
            throw promise;
        }
        
    }
    // 执行入口函数
    try {
        func();
    } catch (error) {
        if(error instanceof Promise) {
            // 不论成功还是失败都重新调用
            error.then(func,func).finally(()=>{
                //恢复原来的值
                window.fetch = oldFetch;
            });
        }
    }
    
}
run(main);

在实际环境中cache需要保存每个请求,给每个请求设置一个key,通过key从缓存中获取每个请求返回的数据。

在react环境中是大量应用这种方式的。react内置组件Suspense,它的作用就是当它子组件出现异步的时候可以等待,并在fallback属性显示一个等待的提示或loading。Suspense内部会捕获promise错误,一旦捕获了就会等待promise完成,在等待期间就会渲染fallback内容,直到promise完成再重新去渲染,也就是会重新调用一次这个函数组件得到新的内容。

const App = ()=> {
    return (
        <div className="app" id='app'>
            <Suspense fallback={<p>Loading user ...</p>}>
                <User/>
            </Suspense>
        </div>
    )
}

User简单示例如下:

import {getData} from '../api/fetchData';

const resource = getData()

const User = () => {
    const userInfo = resource.read();
    return (
        <div>{userInfo.name}</div>
    )

}
export {
    User
}
上一篇 下一篇

猜你喜欢

热点阅读