通过维护一个以唯一标识为键的 promise 缓存对象,在发起请求前检查该键是否已存在。如果存在则直接返回缓存中的 promise,否则创建新 promise、存入缓存并在其 finally 块中移除记录,从而实现同一时刻相同任务的自动去重。

deduplicatePromise 函数如何管理缓存生命周期
核心逻辑在于利用 finally 钩子清理缓存。当 Promise 进入终态(无论是 resolved 还是 rejected),都会触发清理逻辑,确保内存不会因长时间未访问而泄漏,同时允许后续的新请求重新创建实例。

const cache = new Map();
function deduplicatePromise(key, fetchFn) {
if (cache.has(key)) {
return cache.get(key);
}
const promise = fetchFn().finally(() => {
cache.delete(key);
});
cache.set(key, promise);
return promise;
}上述代码展示了完整的实现。调用 deduplicatePromise 时,若 key 已在 cache 中,直接返回旧实例;否则执行 fetchFn 并将结果存入 Map。.finally() 保证了无论成功或失败,任务结束后都会从缓存中删除该键,释放资源。
立即学习“Java免费学习笔记(深入)”;
如何处理并发请求与异常状态下的去重行为
当多个相同的请求并发到达时,只有第一个会真正执行网络操作,其余均共享同一个 Promise 对象。如果请求失败,finally 依然会执行并清除缓存,这意味着后续的重复请求会被视为新任务重新发起,而不是继续等待失败的 Promise。

// 模拟异步请求
async function fetchData(id) {
console.log(`Fetching ${id}...`);
return new Promise(resolve => setTimeout(() => resolve({ id }), 1000));
}
// 测试并发去重
Promise.all([
deduplicatePromise('user-1', () => fetchData(1)),
deduplicatePromise('user-1', () => fetchData(1)),
deduplicatePromise('user-1', () => fetchData(1))
]).then(results => {
console.log('Results:', results.length); // 输出 3 个相同结果
});在并发场景下,三次调用 deduplicatePromise('user-1', ...) 仅触发一次 fetchData。三个调用者获得的是同一个 Promise 引用,因此 results 数组包含三个相同的对象。若将 fetchData 改为抛出错误,finally 仍会清理缓存,确保系统状态的一致性。
基于 finally 清理机制的具体问题:缓存何时被移除
缓存的移除严格依赖于 Promise 进入终态的时刻。只要 Promise 处于 pending 状态,对应的键就始终保留在 cache 中。一旦 resolve 或 reject 被调用,finally 回调立即执行,键值对随即被 delete 移除。
这一机制确保了“去重”仅作用于当前正在进行的异步操作。对于已完成的任务,缓存不再保留任何痕迹,避免了长期占用内存。开发者无需手动管理过期时间或主动清理,只需依赖标准的 Promise 生命周期即可。


















