可用Promise封装带内存缓存的异步请求函数,核心是缓存pending的Promise实例而非结果值,从而避免重复请求与响应错乱;支持TTL过期、手动清理及语义化key生成。

可以用 Promise 封装一个带内存缓存的异步请求函数,核心思路是:对相同参数的请求,只发起一次真实请求,后续直接返回已缓存的 Promise(不是缓存结果值本身),避免重复触发、竞态和多次执行。
缓存的是 Promise 而非结果值
关键点在于缓存正在 pending 的 Promise 实例,而不是缓存 resolve 后的值。这样能天然解决并发请求时的“重复发起”和“响应错乱”问题(比如两个几乎同时的请求,第二个不用等第一个完成后再发,而是直接复用第一个的 Promise)。
示例实现:
const cache = new Map();
<p>function fetchWithCache(url, options = {}) {
const cacheKey = JSON.stringify({ url, options });</p><p>if (cache.has(cacheKey)) {
return cache.get(cacheKey);
}</p><p>const promise = fetch(url, options)
.then(res => {
if (!res.ok) throw new Error(<code>HTTP ${res.status}</code>);
return res.json();
})
.finally(() => cache.delete(cacheKey)); // 请求结束后自动清理(可选)</p><p>cache.set(cacheKey, promise);
return promise;
}支持手动清理与过期控制
实际项目中常需控制缓存生命周期。可在缓存项中额外记录时间戳,并在读取时判断是否过期:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
立即学习“Java免费学习笔记(深入)”;
- 用对象代替纯 Promise 存储:
{ promise, timestamp } - 设置 TTL(如 5 分钟),读取前检查
Date.now() - timestamp > ttl - 过期后删除旧缓存,发起新请求并更新
- 提供
clearCache(key)或clearAll()方法便于调试或登录态变更后清理
处理不同参数但语义相同的请求
上面用 JSON.stringify({ url, options }) 作 key 简单直接,但有局限:
- 对象属性顺序不同会导致 key 不同(可用
canonicalize库或排序键名预处理) - 函数、undefined、Symbol 等无法被 JSON 序列化,建议过滤掉或报错提示
- 若只依赖 URL 缓存(忽略 headers 等),可改用
url字符串作 key,更轻量
进阶:支持 abort 与取消语义
原生 fetch 支持 AbortSignal,但缓存中的 Promise 无法被外部中断。若需取消能力,可:
- 不缓存原始 Promise,改缓存「请求配置」,每次调用都新建
AbortController - 或封装成返回
{ promise, abort }的对象,由调用方决定是否取消(此时缓存逻辑需配合 abort 后清理) - 更稳妥的做法是:仅缓存幂等性 GET 请求,对 POST/PUT 等默认不缓存

















