如何消除异步 async 的传染性呢?
1. 问题现象
有一段代码如下:由于在 getUser 函数中调用了 fetch 函数,导致多个函数中彼此调用并逐层使用 await,从而使每个函数都变成了 async 函数。这导致整个调用链条都变成了异步操作,增加了代码复杂度,这也就是异步的传染性。
async function getUser() {// 异步加载return await fetch('https://jsonplaceholder.typicode.com/users');
}async function m1() {const user = await getUser();// other wordsreturn user;
}async function m2() {const user = await m1();// other wordsreturn user;
}async function m3() {const user = await m2();// other wordsreturn user;
}async function main() {const user = await m3();// other wordsreturn user;
}
这段代码本身没有什么影响,但是在函数式编程中,会有很大的影响。
要求:将所有的 async 全部去掉但不影响代码的运行。
2. 解决方法
这个现象的根源在于 fetch 函数是异步的,进而导致调用者都是 async 函数。那能不能将 fetch 函数修改呢?不可以,因为网络通信需要一定的时间。
如何处理呢?
核心思想是:抛出一个未决的 Promise,并利用调用方来监听这个 Promise 的完成状态,而不是在每个函数里直接处理 async 和 await,实现 "暂停等待" 的效果。
简要代码如下:
// 模拟一个简单的 fetch 函数
function mockFetch(url) {return new Promise((resolve, reject) => {console.log('Fetching data from:', url);setTimeout(() => {if (url === 'https://jsonplaceholder.typicode.com/users') {resolve({json: () => Promise.resolve([{ id: 1, name: 'John Doe' }]),});} else {reject('Invalid URL');}}, 1000);});
}function getUser() {// 异步获取数据,不使用 async/awaitreturn fetch('https://jsonplaceholder.typicode.com/users');
}function m1() {const user = getUser(); // 获取用户return user;
}function m2() {const user = m1(); // 调用 m1return user;
}function m3() {const user = m2(); // 调用 m2return user;
}function main() {const user = m3(); // 调用 m3return user;
}function execute(fn) {// 1. 替换 fetch 函数,模拟请求缓存机制const oldFetch = fetch; // 保存原始 fetchconst cache = {status: 'pending', // 状态:'pending', 'fulfilled', 'rejected'value: null, // 缓存的值};function newFetch(...args) {// 如果缓存状态是 fulfilled,直接返回缓存结果if (cache.status === 'fulfilled') {return Promise.resolve(cache.value);} // 如果缓存状态是 rejected,抛出缓存的错误if (cache.status === 'rejected') {throw cache.value;}// 如果没有缓存,发起请求并保存状态const promise = oldFetch(...args).then((res) => res.json()).then((data) => {cache.value = data;cache.status = 'fulfilled'; // 请求成功,缓存数据}).catch((error) => {cache.value = error;cache.status = 'rejected'; // 请求失败,缓存错误throw error;});// 抛出 promise 以暂停执行throw promise;}window.fetch = newFetch; // 替换 fetch 函数// 2. 尝试执行 fn 函数,如果捕获到 promise,等待其完成后重新执行try {fn(); // 尝试执行函数} catch (error) {if (error instanceof Promise) {// 捕获 promise,等待完成后重新执行error.then(() => {window.fetch = newFetch; // 保持 fetch 替换为新 fetchfn(); // 请求完成后重新执行window.fetch = oldFetch; // 恢复旧 fetch}).catch((err) => {console.error('Request failed:', err);});}}window.fetch = oldFetch; // 恢复 fetch
}// 替换为模拟 fetch 函数
window.fetch = mockFetch;// 执行代码
execute(main);
展示如下:main 函数成功执行两次。
代码上包含详细的解释,可以尝试一下这种方法。