promise状态不可逆,缓存需存储终态值而非实例;首次调用执行异步并缓存结果,后续调用用promise.resolve/reject返回新promise;支持无参与带参(memoization)两种实现,需注意pending状态不缓存、内存泄漏及参数序列化一致性。

Promise 本身是“一次性”的,一旦 settled(fulfilled 或 rejected),状态不可逆、不可重置。所谓“支持状态持久化的 Promise 缓存”,不是让单个 Promise 对象反复使用,而是缓存 Promise 的执行结果(value 或 reason),后续调用直接返回一个已 settled 的新 Promise,从而避免重复执行异步逻辑。
核心思路:缓存 resolved/rejected 状态的结果
关键在于不缓存 Promise 实例本身(因 Promise 无法重用),而是缓存其终态值(成功值或错误原因),再用 Promise.resolve() 或 Promise.reject() 包装生成新的、已确定状态的 Promise。
- 首次调用:执行异步操作,记录结果(value 或 error),并缓存
- 后续调用:跳过执行,直接返回基于缓存结果构造的新 Promise
- 缓存可基于参数做键(如 JSON.stringify(args)),实现参数化记忆(memoization)
基础缓存实现(无参场景)
适用于不依赖参数的纯异步操作,例如初始化配置加载:
function createCachedPromise(fn) {
let cache = null;
let promise = null;
return function() {
if (promise) return promise;
promise = fn().then(
value => {
cache = { type: 'fulfilled', value };
return value;
},
reason => {
cache = { type: 'rejected', reason };
throw reason;
}
);
return promise;
};
}
// 使用示例
const loadConfig = createCachedPromise(() => fetch('/api/config').then(r => r.json()));
loadConfig(); // 触发请求
loadConfig(); // 返回同一个 Promise(已 pending)或后续都返回已 fulfilled 的 Promise
带参数的健壮缓存(推荐)
多数场景需支持参数(如 getUser(id))。需用参数生成唯一 key,并分别缓存每个 key 对应的结果:
function memoizePromise(fn, getKey = JSON.stringify) {
const cache = new Map();
return async function(...args) {
const key = getKey(args);
if (cache.has(key)) {
const cached = cache.get(key);
if (cached.type === 'fulfilled') {
return cached.value;
} else {
throw cached.reason;
}
}
try {
const value = await fn.apply(this, args);
cache.set(key, { type: 'fulfilled', value });
return value;
} catch (reason) {
cache.set(key, { type: 'rejected', reason });
throw reason;
}
};
}
// 使用示例
const getUser = memoizePromise(async (id) => {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
});
getUser(123); // 发起请求
getUser(123); // 立即 resolve 缓存值
getUser(456); // 新 key,重新请求
注意事项与边界处理
- 避免缓存 pending 状态:不要在 Promise 还没 settle 时就存入缓存,否则并发调用会共享同一个未完成 Promise,导致“竞态”而非“复用”。上面实现中只在 then/catch 内缓存,是安全的
-
内存泄漏风险:长期运行需考虑缓存清理策略(如 TTL 过期、LRU 限制大小),可用
Map配合时间戳或第三方库(如lru-cache)增强 -
引用类型参数慎用默认 getKey:
JSON.stringify({a:1})和JSON.stringify({a:1, b:undefined})结果相同,建议显式定义 getKey 函数保证语义一致性 - 不适用于实时性要求高的场景:缓存的是快照,若数据可能被外部修改,需配合失效机制(如手动 clear、watch 事件等)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











