最近看到了一个非常有意思的所谓的大厂的面试题,在网上也引起了很大的争议。那就是async/await消除异步的传染性,以fetch函数为例,通过侵入式修改fetch方法,来实现消除异步的传染性的方案。

这种方案其实是一股脑的借鉴一些框架的实现,如react框架中的父组件加载子组件的实现。

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

但是框架这样设计有框架的定位,我们可以根据这种方法扩展思维,但是不建议在生产环境中使用。这种方法其实仅仅只是为了消除所谓的传染性,对于实际业务实现没有实质效益。

接下来看下实现思路吧

当一个 fetch 请求返回 promise 时,需要使用 await 来获取数据。而一旦使用了 await,当前函数就必须是 async 函数。如此循环往复地调用,接下来所有的方法都得加上 async await,这就是所谓的“异步传染性”。

async function reqRes() {
    return await fetch('https://jsonplaceholder.typicode.com/posts/1');
}

async function async() {
    return await reqRes();
}

async function main() {
    let user = await a();
    console.log(user)
}

main();

如何消除上面的现象呢?这就不得不说下面的这种方案啦。

QQ截图20240814100028.png

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

在调用fetch的时候不等待了而是报错,这样所有函数都终止了,调用栈层层弹出,调用结束。但是我们最终的目的是要拿到结果的,前面虽然报错了,网络线程仍然还在继续网络请求它不会停止,直到拿到结果。

拿到结果后我们把它放在一个缓存中,接着再去恢复整个调用链的执行。再执行fetch时,结果已经缓存在cache了,取出数据就可以直接交付不用等待了从而变成了同步函数。整个过程会走两次,第一次以错误结束,第二次以成功结束,这两次都是同步的。

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

代码实现

 function start(func) {
        const oldFetch = fetch;
        const cache = {
            status: 'PENDING',
            value: null
        }

        function newFetch(...args) {
            if (cache.status === 'FULFILLED') {
                return cache.value;
            }
            if (cache.status === 'REJECTED') {
                throw  cache.value;
            }
            throw oldFetch(...args).then(function (res) {
                return res.json();
            }).then(function (data) {
                cache.status = 'FULFILLED';
                cache.value = data;
            }).catch(function (error) {
                cache.status = 'REJECTED';
                cache.value = error;
            });
        }

        window.fetch = newFetch;

        try {
            func();
        } catch (error) {
            if (error instanceof Promise) {
                error.finally(function () {
                    window.fetch = newFetch;
                    func();
                    window.fetch = oldFetch;
                })
            }
        }
        window.fetch = oldFetch;
    }

    function reqRes() {
        return fetch('https://jsonplaceholder.typicode.com/posts/1');
    }

    function a() {
        return reqRes();
    }

    function main() {
        console.log('main');
        let res = a();
        console.log(res)
    }
    start(main)

对代码的进一步分析

start 函数中:

  • 它先保存了原始的 fetch 方法。
  • 定义了一个 cache 对象来记录异步操作的状态和结果。
  • newFetch 函数根据 cache 的状态来决定直接返回结果或执行原始 fetch 并处理其后续的解析和状态更新。如果遇到错误则更新状态并抛出。
  • 然后在执行 func(即 main 函数)时,如果遇到一个 Promise 类型的异常,在其 finally 中先恢复 newFetch ,重新执行 func ,最后再恢复原始 fetch

整体来说

  • 这个代码试图通过一些自定义的逻辑来控制和管理异步 fetch 操作及其相关的状态和流程,可能是为了在特定场景下实现一些特殊的行为或控制机制。但这种方式可能会引入一些复杂性和潜在的问题,比如对 fetch 的修改可能会影响到其他依赖于标准 fetch 行为的部分,并且异常处理的方式也需要谨慎考虑其正确性和合理性。
Last modification:August 14, 2024
如果觉得我的文章对你有用,请随意赞赏