如何在 JavaScript 中实现可选且仅执行一次的 fetch 请求

11次阅读

如何在 JavaScript 中实现可选且仅执行一次的 fetch 请求

本文介绍一种高效模式,通过缓存 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 免费学习笔记(深入)”;

text=ZqhQzanResources