
本文介绍一种高效模式,通过缓存 promise 实现对同一 url 的 fetch 请求“懒加载”与“单次执行”,避免重复网络请求,适用于多个逻辑分支条件性触发同一资源获取的场景。
本文介绍一种高效模式,通过缓存 promise 实现对同一 url 的 fetch 请求“懒加载”与“单次执行”,避免重复网络请求,适用于多个逻辑分支条件性触发同一资源获取的场景。
在前端开发中,我们常遇到这样的需求:多个异步流程可能按需、非确定性地请求同一个远程资源(如 /api/config 或 /api/feature-flag),但该资源获取开销较大(响应慢、计算重或有调用频次限制)。此时,若不加协调,多个并发调用会触发多次重复 fetch,既浪费带宽与服务端资源,又可能导致数据不一致或状态错乱。
理想的解决方案应满足三点:
✅ 懒加载(Lazy):首次调用时才发起请求;
✅ 单次执行(Singleton Promise):无论被多少处代码调用,底层 fetch 仅执行一次;
✅ 自动共享结果:后续调用直接复用已解析的 Promise 结果(含成功值或失败原因)。
✅ 正确实现:利用 ??= 缓存 Promise 实例
核心思路是将 fetch 封装为一个函数,其内部维护一个私有变量(如 cPromise),并使用 Nullish Coalescing Assignment (??=) 确保该 Promise 仅被初始化一次:
let cPromise;
const fetchC = () => {
// 仅当 cPromise 为 null/undefined 时,才执行右侧表达式
return (cPromise ??= fetch('c')
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
})
);
};
// 分支逻辑 A:根据 /a 的响应决定是否需要 /c
fetch('a')
.then(res => res.json())
.then(data => {
if (data.value === '1') {
fetchC().then(result => console.log('through a', result));
}
});
// 分支逻辑 B:根据 /b 的响应决定是否需要 /c
fetch('b')
.then(res => res.json())
.then(data => {
if (data.value === '1') {
fetchC().then(result => console.log('through b', result));
}
});? 关键点解析:
- cPromise ??= ... 等价于 cPromise = cPromise ?? (...),即“仅当 cPromise 是 null 或 undefined 时才赋值”,天然支持惰性初始化;
- fetchC() 每次返回的是同一个 Promise 实例(而非新建 Promise),因此 .then() 注册的回调会自动排队等待首次 resolve/reject;
- 错误处理已内嵌:若 fetch('c') 失败(如 404、网络中断),cPromise 仍会被赋值为一个 rejected Promise,后续调用将立即收到相同错误,符合预期行为。
⚠️ 注意事项与最佳实践
- 不要在函数内 new Promise(...) 包裹 fetch:这会破坏 Promise 的共享性,导致每次调用都新建异步任务;
-
避免全局污染:生产环境中建议将 cPromise 封装进模块作用域或闭包,例如使用 IIFE 或 ES 模块导出:
// utils/fetchOnce.js let cPromise; export const fetchC = () => (cPromise ??= fetch('c').then(r => r.json())); - 支持重试?谨慎扩展:若需失败后重试,不应简单清空 cPromise(会破坏幂等性),而应设计带重试策略的独立封装(如 retryableFetchC()),并明确区分“缓存失效”语义;
-
TypeScript 用户提示:可添加类型注解提升安全性:
let cPromise: Promise<MyDataType> | undefined; const fetchC = (): Promise<MyDataType> => (cPromise ??= fetch('c').then(r => r.json()));
✅ 总结
通过 ??= 运算符配合模块级 Promise 缓存,我们以极简代码实现了「按需触发、最多一次、结果共享」的 fetch 行为。它无需引入额外库(如 RxJS 或自定义 Promise 工具类),兼容现代浏览器与 Node.js,并具备良好的可读性与可维护性。当你面对多入口、条件性资源加载场景时,这种模式值得作为标准实践纳入工具箱。
立即学习“Java免费学习笔记(深入)”;










