消除异步的传染性

2,122 阅读4分钟

什么是异步传染性?

image.png

如上代码所示,我们在执行 main 函数时返回了一个 promise 的执行结果,因为 js 是单线程,如果想要获取异步代码并且执行顺序还得是同步的话,我们必须加上 async 和 await 去等待结果的反馈。 那么这就导致了如果要使用 async 和 await 语法糖必须在函数使用的链路上全部都要使用 async 和 await,这就是异步传染性。

image.png

目标

有些同学可能觉得这没什么,就是有点恶心罢了。可是如果是函数式编程的话就会形成很大的问题,本来其他函数是好好的纯函数,结果全部变成了副作用,那么我们要做的就是两件事。

  1. 要将异步函数变成纯函数的同步调用方式。
  2. 保持结果不变。

思考

由于我们代码变成了同步调用的方式,那么我们就要要求 fetch 函数立即返回结果,可是 fetch 是异步函数,请求需要一定的时间,现在我们又要要求 fetch 立即返回结果,可是 fetch 做不到啊!

image.png

那么 fetch 返回不了怎么办呢?还能怎么办,报错呗,有且只有报错的一条路。

既然我们必须要从 fetch 入手,且 fetch 只有报错一条路,那么我们的解决思路也就只在这一条路之中,我们可以用下图这种设计思路来解决这个问题。

image.png

综上所述思路我们已经清晰了,那么让我们用代码来实现吧!

image.png

代码实现

沿着思考的思路我们开始实现代码,首先我们要保证不能对这些函数有侵入权,那什么是侵入性呢?侵入性就是我们不能去修改函数里边的代码,那么我们只能在终点的地方做文章,也就是在 main 函数的调用上做文章。

image.png

当然现在代码起不到任何作用,我们必须要变成思考的那种模式,我们就必须要改变 fetch,改成一个新的函数,这样一来就可以保证在这个函数运行期间,fetch 函数使用的是我们改变的新的函数,而新函数的逻辑就是我们思考的逻辑。

image.png

那么 fetch 函数要做什么事情呢?无非就以下两种情况。

  1. 发送请求报错,再次请求。
  2. 有缓存的时候交付缓存结果,没缓存的时候储存缓存结果。

因为我们在 fetch 执行时首先要判断是否有缓存,是否需要储存缓存,所以我们先来实现这个情况。

image.png

那么我们实现了缓存命中的情况,接下来就是实现发送请求,最后实现报错。

image.png

写到这里就剩下报错还没有实现,也就是最关键的一条线,我们怎么在报错的时候再次请求数据呢?我们接着往下看。

image.png

到这里我们的思考已经变成了现实,控制台中也输出了正确的数据。

image.png

image.png

总结

这里的代码每一行我们都认识,但是连起来可能就没那么简单了,当然这只是非常粗略的代码,我们有很多的东西没有考虑到,但是这个方向是没有问题的,因为这种类似的思路在 React 里已经在使用了。

我们来看一个在 React 中使用的例子,这里边有一个非常神奇的现象,代码非常简单。

image.png

以下就是我们这次的所有代码。

function getUser() {
  return fetch("https://my-json-server.typicode.com/typicode/demo/profile");
}
function m1() {
  // other works
  return getUser();
}
function m2() {
  // other works
  return m1();
}

function m3() {
  // other works
  return m2();
}

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

function run(func) {
  let cache = [];
  let i = 0;
  const _originalFetch = window.fetch;
  window.fetch = (...args) => {
    if (cache[i]) {
      if (cache[i].status === "fulfilled") {
        return cache[i].data;
      } else if (cache[i].status === "rejected") {
        throw cache[i].err;
      }
    }
    const result = {
      status: "pending",
      data: null,
      err: null,
    };
    cache[i++] = result;
    const prom = _originalFetch(...args)
      .then((resp) => resp.json())
      .then(
        (resp) => {
          result.status = "fulfilled";
          result.data = resp;
        },
        (err) => {
          result.status = "rejected";
          result.data = err;
        }
      );
    throw prom;
  };
  try {
    func();
  } catch (err) {
    if (err instanceof Promise) {
      const reRun = () => {
        i = 0;
        func();
      };
      err.then(reRun, reRun);
    }
  }
}

run(main);

备注:文章的学习来源为,抖音的渡一Web前端学习频道