我们看下下面函数的调用:
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
请求,导致了异步的产生。
网络传输是需要时间的,这个是无法改变的。让浏览器完全阻塞那就卡死了,肯定是行不通的。
目前的函数调用流程如下:
main->getUser->fetch - -> 等待网络请求,请求完成 --> getUser->main
由于fetch需要等待导致所有相关的函数都要等待。那么只能在fetch这里做一些操作了。如何让fetch不等待,就只能报错了。
我们看下通过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
}