通过拦截 promise 的 .then() 或 .catch() 调用,在内部维护一个以参数为键的缓存对象来实现。具体做法是创建一个包装函数,每次调用时先检查缓存中是否存在该参数的结果;若存在且未过期,直接返回缓存的 promise;若不存在,则执行原始异步操作并将生成的 promise 存入缓存,同时利用 promise.finally() 清理可能失败的中间状态。

如何构建带失效时间的缓存包装器
实现的核心在于将原始函数与缓存逻辑解耦,并引入时间戳控制有效期。我们定义一个 createCachedPromise 函数,它接收原始异步函数和可选的 TTL(生存时间)配置。当用户请求数据时,系统首先计算当前时间与上次访问时间的差值,如果小于 TTL 则复用旧 Promise,否则触发新请求并更新缓存记录。这种机制确保了高频调用的稳定性,同时避免了内存无限增长。

function createCachedPromise(fn, ttl = 5000) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
const now = Date.now();
const cached = cache.get(key);
if (cached && (now - cached.timestamp {
// 清理失败或过期的中间态
if (cache.get(key)?.promise === promise) {
cache.delete(key);
}
});
cache.set(key, { promise, timestamp: now });
return promise;
};
}
如何处理并发请求导致的重复执行
在并发场景下,多个相同的请求可能在缓存检查之后、写入之前同时到达,导致原始函数被多次执行。解决此问题的关键在于确保同一个 Key 在同一时刻只对应一个正在进行的 Promise 实例。我们在生成新 Promise 后,立即将其绑定到缓存中,后续请求即使在该 Promise 尚未 resolve 时到达,也会命中缓存中的引用,从而共享同一个执行结果。此外,使用 Map 而非普通对象可以支持更复杂的键类型,避免序列化冲突。

// 模拟原始异步函数
const fetchUser = async (id) => {
console.log(`Fetching user ${id}...`);
return { id, name: 'Alice' };
};
// 创建缓存包装器
const getCachedUser = createCachedPromise(fetchUser, 10000);
// 并发调用
Promise.all([
getCachedUser(1),
getCachedUser(1),
getCachedUser(1)
]).then(results => {
console.log('Results:', results);
});
// 输出:仅打印一次 "Fetching user 1..."
基于特定输入条件如何验证缓存是否生效
为了验证缓存机制在实际运行中的表现,我们可以观察相同参数下的日志输出次数。当传入完全相同的参数数组时,第二次及以后的调用不会触发原始函数的执行体,而是直接返回之前的 Promise 对象。这可以通过比较返回对象的引用地址来确认:getCachedUser(1) === getCachedUser(1) 将返回 true。若参数顺序不同或类型变化,例如传入字符串 '1' 而非数字 1,由于 JSON 序列化后的键值不同,系统将视为新请求并重新执行异步操作。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











