
本文介绍如何通过 promise 缓存机制,确保一个可选的 fetch 请求(如 url 'c')在多处调用时仅执行一次,并将结果共享给所有后续调用者,避免重复网络请求和资源浪费。
本文介绍如何通过 promise 缓存机制,确保一个可选的 fetch 请求(如 url 'c')在多处调用时仅执行一次,并将结果共享给所有后续调用者,避免重复网络请求和资源浪费。
在实际前端开发中,常遇到这样的场景:多个逻辑分支可能需要获取同一份远程数据(例如配置、权限信息或共享资源),但该请求开销较大(耗时长、带宽高或服务端有频控)。若不加协调,各分支独立发起 fetch,将导致冗余请求、响应不一致甚至服务端压力激增。
核心诉求是:按需触发、首次执行、结果复用、线程安全(Promise 天然支持)。JavaScript 的 Promise 具备“一旦 resolve/reject,后续 .then() 均立即获得缓存结果”的特性,这正是实现“单次执行、多次消费”的理想基础。
✅ 正确解法:惰性初始化 + Nullish Coalescing Assignment(??=)
以下为推荐实现:
// 声明一个模块级(或闭包内)变量,用于缓存 Promise 实例
let cPromise;
// 封装 fetch('c') 为惰性函数:仅首次调用时发起请求,之后始终返回已缓存的 Promise
const fetchC = () => (cPromise ??= fetch('c').then(res => res.json()));
// 分支 A:根据 a 的响应决定是否需要 c
fetch('a')
.then(res => res.json())
.then(data => {
if (data.value === '1') {
fetchC().then(cData => console.log('through a', cData));
}
});
// 分支 B:同理
fetch('b')
.then(res => res.json())
.then(data => {
if (data.value === '1') {
fetchC().then(cData => console.log('through b', cData));
}
});? 关键原理说明:
- cPromise ??= fetch(...) 使用 Nullish Coalescing Assignment(??=) 操作符:仅当 cPromise 为 null 或 undefined 时才执行右侧表达式并赋值;否则直接返回原值。
- 因此,fetchC() 第一次被调用时,cPromise 为 undefined,触发 fetch('c') 并将其返回的 Promise(经 .then() 转换后)赋给 cPromise;
- 后续所有调用均跳过 fetch,直接返回已缓存的 Promise —— 即使 fetch('c') 尚未完成,后续 .then() 也会正确排队等待其 resolve。
? 注意:cPromise 必须声明在函数作用域之外(如模块顶层、类字段或闭包变量),否则每次调用 fetchC 都会重新初始化,失去缓存意义。
立即学习“Java免费学习笔记(深入)”;
⚠️ 常见误区与避坑指南
| 错误写法 | 问题分析 |
|---|---|
| const c = () => new Promise(...) | 每次调用都新建 Promise,fetch 必然重复执行 |
| let cPromise; const c = () => { cPromise = fetch(...); return cPromise; } | 缺少判空逻辑,每次调用都会重置 cPromise 并重复 fetch |
| 在 async/await 中未 await 缓存 Promise | 如 const data = fetchC(); console.log(data) —— 打印的是 Promise 对象而非结果,应 await fetchC() 或链式 .then() |
✅ 进阶建议(生产环境增强)
-
错误处理统一化:在 fetchC 中加入 .catch() 并缓存 reject 状态,避免重复失败重试:
const fetchC = () => (cPromise ??= fetch('c') .then(res => { if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); }) .catch(err => { console.error('Failed to fetch C:', err); throw err; }) ); -
支持强制刷新:添加 force = false 参数,便于调试或兜底策略:
const fetchC = (force = false) => force ? (cPromise = fetch('c').then(r => r.json())) : (cPromise ??= fetch('c').then(r => r.json())); -
TypeScript 类型提示(如使用 TS):
let cPromise: Promise<MyCDataType> | undefined; const fetchC = (): Promise<MyCDataType> => (cPromise ??= fetch('c').then(r => r.json() as Promise<MyCDataType>));
✅ 总结
通过 let 变量 + ??= 操作符 + Promise 缓存,即可优雅实现“按需、单次、共享”的可选请求模式。该方案轻量、标准、无依赖,兼容所有现代浏览器及 Node.js(配合 node-fetch),是处理条件性远程数据加载的推荐实践。记住核心口诀:“定义在外,惰性在内,赋值靠 ??=,复用靠 Promise”。










