前言
最近在阅读 React 源码,发现了一个很有意思的现象,就是 React.Suspense 的原理,它使得异步函数不再具有传染性,即异步函数不再需要层层传递,而是直接在顶层进行控制。
异步函数的传染性
在 JavaScript 中,异步函数通常是通过回调函数来实现的,例如:
function fetchData(callback) {
setTimeout(() => {
callback("data");
}, 1000);
}
fetchData((data) => {
console.log(data);
});
在上面的例子中,fetchData 函数是一个异步函数,它接受一个回调函数作为参数,当数据获取完成后,调用回调函数将数据传递出去。
但是,如果需要在一个异步函数中调用另一个异步函数,那么就需要将回调函数传递下去,例如:
function fetchData(callback) {
setTimeout(() => {
callback("data");
}, 1000);
}
function fetchData2(callback) {
fetchData((data) => {
callback(data + "2");
});
}
fetchData2((data) => {
console.log(data);
});
在上面的例子中,fetchData2 函数是一个异步函数,它调用了 fetchData 函数,并将回调函数传递给了 fetchData 函数。这样,当 fetchData 函数获取到数据后,就会调用回调函数将数据传递给 fetchData2 函数,然后 fetchData2 函数再调用回调函数将数据传递给顶层函数。
但是,如果需要在一个异步函数中调用多个异步函数,那么就需要将回调函数层层传递下去,例如:
function fetchData(callback) {
setTimeout(() => {
callback("data");
}, 1000);
}
function fetchData2(callback) {
fetchData((data) => {
callback(data + "2");
});
}
function fetchData3(callback) {
fetchData2((data) => {
callback(data + "3");
});
}
fetchData3((data) => {
console.log(data);
});
在上面的例子中,fetchData3 函数是一个异步函数,它调用了 fetchData2 函数,并将回调函数传递给了 fetchData2 函数。然后 fetchData2 函数再调用了 fetchData 函数,并将回调函数传递给了 fetchData 函数。这样,当 fetchData 函数获取到数据后,就会调用回调函数将数据传递给 fetchData2 函数,然后 fetchData2 函数再调用回调函数将数据传递给 fetchData3 函数,最后 fetchData3 函数再调用回调函数将数据传递给顶层函数。
可以看到,异步函数的传染性会导致代码变得非常复杂,难以维护。因此,我们需要一种更好的方式来处理异步函数,使得异步函数不再具有传染性。
async/await
async/await 是 JavaScript 中处理异步函数的一种方式,它使得异步函数不再具有传染性,而是直接在顶层进行控制。例如:
async function fetchData() {
const data = await new Promise((resolve) => {
setTimeout(() => {
resolve("data");
}, 1000);
});
console.log(data);
}
fetchData();
在上面的例子中,fetchData 函数是一个异步函数,它使用 await 关键字来等待 Promise 对象的解析,然后将解析结果赋值给 data 变量。这样,当 Promise 对象解析完成后,fetchData 函数就会继续执行,将 data 变量的值打印出来。
可以看到,使用 async/await 可以使得异步函数不再具有传染性,而是直接在顶层进行控制。这样,代码就变得更加简洁,易于维护。
React.Suspense
React.Suspense 是 React 中处理异步组件的一种方式,它使得异步组件不再具有传染性,而是直接在顶层进行控制。例如:
import React, { Suspense } from "react";
import { unstable_createResource } from "react-cache";
const resource = unstable_createResource(fetchData);
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("data");
}, 1000);
});
}
function MyComponent() {
const data = resource.read();
return <div>{data}</div>;
}
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
);
}
export default App;
在上面的例子中,MyComponent 函数是一个异步组件,它使用 unstable_createResource 函数创建了一个资源对象 resource,然后使用 resource.read() 方法来获取数据。这样,当数据获取完成后,MyComponent 函数就会继续执行,将数据的值渲染到页面上。 React.Suspense 使用了 unstable_createResource 函数来创建资源对象,这个函数接受一个异步函数作为参数,当异步函数执行完成后,就会将解析结果赋值给资源对象。然后,React.Suspense 使用资源对象的 read() 方法来获取数据,当数据获取完成后,React.Suspense 就会继续执行,将数据的值渲染到页面上。
可以看到,React.Suspense 使得异步组件不再具有传染性,而是直接在顶层进行控制。这样,代码就变得更加简洁,易于维护。
仿照 React.Suspense 的原理 解读一道面试题
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);
}
main();
面临的问题
因为上述都是一个个简单的纯函数,但是因为涉及到异步调用,就产生了副作用,那现在希望全部可以正常同步执行,而且又可以正常返回结果,该怎么做呢? 于是改造的结果如下,变成同步执行
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);
}
main();
我们希望可以同步调用,该如何实现呢?

解决方案
function run(func) {
//保存旧的fetch
const oldFetch = window.fetch;
//重写fetch
const cache = {
status: "pending", //pending, fullfilled, rejected
value: null,
};
function newFetch(...args) {
//有缓存 交付缓存
if (cache.status === "fulfilled") {
return cache.value;
} else if (cache.status === "rejected") {
throw cache.value;
}
//无缓存
const p = oldFetch(...args)
.then((res) => res.json())
.then((data) => {
cache.status = "fulfilled";
cache.value = data;
})
.catch((err) => {
cache.status = "rejected";
cache.value = err;
});
// 立即抛出错误
throw p;
}
window.fecth = newFetch;
//执行函数
try {
func();
} catch (e) {
if (e instanceof Promise) {
e.finally(() => {
window.fetch = newFecth;
func();
window.fetch = oldFetch;
});
}
}
//恢复旧的fetch
window.fetch = oldFetch;
//返回结果
}
run(main);
总结
异步函数的传染性会导致代码变得非常复杂,难以维护。因此,我们需要一种更好的方式来处理异步函数,使得异步函数不再具有传染性。async/await 和 React.Suspense 都可以使得异步函数不再具有传染性,而是直接在顶层进行控制。这样,代码就变得更加简洁,易于维护。