什么是异步传染性?
如上代码所示,我们在执行 main 函数时返回了一个 promise 的执行结果,因为 js 是单线程,如果想要获取异步代码并且执行顺序还得是同步的话,我们必须加上 async 和 await 去等待结果的反馈。 那么这就导致了如果要使用 async 和 await 语法糖必须在函数使用的链路上全部都要使用 async 和 await,这就是异步传染性。
目标
有些同学可能觉得这没什么,就是有点恶心罢了。可是如果是函数式编程的话就会形成很大的问题,本来其他函数是好好的纯函数,结果全部变成了副作用,那么我们要做的就是两件事。
- 要将异步函数变成纯函数的同步调用方式。
- 保持结果不变。
思考
由于我们代码变成了同步调用的方式,那么我们就要要求 fetch
函数立即返回结果,可是 fetch
是异步函数,请求需要一定的时间,现在我们又要要求 fetch
立即返回结果,可是 fetch
做不到啊!
那么 fetch
返回不了怎么办呢?还能怎么办,报错呗,有且只有报错的一条路。
既然我们必须要从 fetch
入手,且 fetch
只有报错一条路,那么我们的解决思路也就只在这一条路之中,我们可以用下图这种设计思路来解决这个问题。
代码实现
沿着思考的思路我们开始实现代码,首先我们要保证不能对这些函数有侵入权,那什么是侵入性呢?侵入性就是我们不能去修改函数里边的代码,那么我们只能在终点的地方做文章,也就是在 main
函数的调用上做文章。
当然现在代码起不到任何作用,我们必须要变成思考的那种模式,我们就必须要改变 fetch
,改成一个新的函数,这样一来就可以保证在这个函数运行期间,fetch
函数使用的是我们改变的新的函数,而新函数的逻辑就是我们思考的逻辑。
那么 fetch
函数要做什么事情呢?无非就以下两种情况。
- 发送请求报错,再次请求。
- 有缓存的时候交付缓存结果,没缓存的时候储存缓存结果。
因为我们在 fetch
执行时首先要判断是否有缓存,是否需要储存缓存,所以我们先来实现这个情况。
那么我们实现了缓存命中的情况,接下来就是实现发送请求,最后实现报错。
写到这里就剩下报错还没有实现,也就是最关键的一条线,我们怎么在报错的时候再次请求数据呢?我们接着往下看。
到这里我们的思考已经变成了现实,控制台中也输出了正确的数据。
总结
这里的代码每一行我们都认识,但是连起来可能就没那么简单了,当然这只是非常粗略的代码,我们有很多的东西没有考虑到,但是这个方向是没有问题的,因为这种类似的思路在 React 里已经在使用了。
我们来看一个在 React 中使用的例子,这里边有一个非常神奇的现象,代码非常简单。
以下就是我们这次的所有代码。
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前端学习频道。