编码母题 / P0 / 异步编程 / 04
并发请求去重与单次执行
先读清题意与边界,独立尝试,再展开思路并对照可运行的参考代码。
重要程度必练难度中等预计完成25–35 分钟
题目是什么
相同 key 的并发调用共享同一次在途任务,不同 key 相互独立。任务结束后删除在途记录;失败也不能被永久缓存。
输入输出示例
两个同时请求同一配置 key 的调用只执行一次加载,但完成后下一次调用会重新执行。
练习起手式与验证
复制代码后补全空方法,再运行下方三组验证;复杂题所需的辅助类型与输入准备一并放在代码块中。
/*
* 题目:并发请求去重与单次执行
* 要求:相同 key 的并发调用共享同一次在途任务,不同 key 相互独立。
* 任务结束后删除在途记录;失败也不能被永久缓存。
* 示例:两个同时请求同一配置 key 的调用只执行一次加载,但完成后下一次调用会重新执行。
*/
class Singleflight {
constructor() {}
do(key, task) {}
has(key) {}
}
(async () => {
const group = new Singleflight();
let calls = 0;
let release;
const gate = new Promise((resolve) => {
release = resolve;
});
const task = async () => {
calls += 1;
await gate;
return 42;
};
const a = group.do("x", task);
const b = group.do("x", task);
console.log(a); // 预期:b
console.log(calls); // 预期:0
release();
console.log(await Promise.all([a, b])); // 预期:[42, 42]
})().catch(console.error);
/*
* 题目:并发请求去重与单次执行
* 要求:相同 key 的并发调用共享同一次在途任务,不同 key 相互独立。
* 任务结束后删除在途记录;失败也不能被永久缓存。
* 示例:两个同时请求同一配置 key 的调用只执行一次加载,但完成后下一次调用会重新执行。
*/
class Singleflight {
constructor() {}
do(key, task) {}
has(key) {}
}
(async () => {
const group = new Singleflight();
let calls = 0;
let release;
const gate = new Promise((resolve) => {
release = resolve;
});
const task = async () => {
calls += 1;
await gate;
return 42;
};
const a = group.do("x", task);
const b = group.do("x", task);
console.log(a); // 预期:b
console.log(calls); // 预期:0
release();
console.log(await Promise.all([a, b])); // 预期:[42, 42]
})().catch(console.error);解题思路
展开思路、不变量与常见边界
关键切入点:Map 保存 key 到在途 Promise;必须先登记再暴露,并在完成时按身份清理。
核心模型:Map<key, Promise>。必须先把 Promise 放入 Map,再暴露给调用方; 在 finally 中删除,并通过身份比较避免旧请求误删同 key 的新请求。 不变量:同一时刻每个 key 最多有一个在途 Promise;完成后无残留记录。 时间复杂度 O(1),空间复杂度 O(k),k 为同时在途的不同 key 数。 常见错误:只在成功时清理;缓存最终值导致语义变成永久缓存;直接 await 后 才写入 Map,形成竞态;一个调用方取消共享底层请求,误伤其他调用方。 迁移:Token 刷新、配置加载、缓存回源、Agent 同一 Tool 查询合并。
参考代码与验证
“关键机制”注释用于解释实现,下载链接提供未经页面注释修改的原始 JS 文件。
// 关键机制:Map 保存 key 到在途 Promise;必须先登记再暴露,并在完成时按身份清理。
class Singleflight {
constructor() {
this.inflight = new Map();
}
do(key, task) {
if (typeof task !== "function")
throw new TypeError("task must be a function");
if (this.inflight.has(key)) return this.inflight.get(key);
const promise = Promise.resolve().then(task);
this.inflight.set(key, promise);
promise
.finally(() => {
if (this.inflight.get(key) === promise) this.inflight.delete(key);
})
.catch(() => {});
return promise;
}
has(key) {
return this.inflight.has(key);
}
}
// 三组以上可直接运行的验证;异步题使用独立执行块。
(async () => {
const group = new Singleflight();
let calls = 0;
let release;
const gate = new Promise((resolve) => {
release = resolve;
});
const task = async () => {
calls += 1;
await gate;
return 42;
};
const a = group.do("x", task);
const b = group.do("x", task);
console.log(a); // 预期:b
console.log(calls); // 预期:0
release();
console.log(await Promise.all([a, b])); // 预期:[42, 42]
console.log(calls); // 预期:1
await Promise.resolve();
console.log(group.has("x")); // 预期:false
const error = new Error("boom");
console.log(await Promise.resolve(group.do("e", () => Promise.reject(error))).then(() => false, () => true)); // 预期:true(拒绝)
await Promise.resolve();
console.log(group.has("e")); // 预期:false
console.log(await group.do("e", () => 7)); // 预期:7
})().catch(console.error);
// 关键机制:Map 保存 key 到在途 Promise;必须先登记再暴露,并在完成时按身份清理。
class Singleflight {
constructor() {
this.inflight = new Map();
}
do(key, task) {
if (typeof task !== "function")
throw new TypeError("task must be a function");
if (this.inflight.has(key)) return this.inflight.get(key);
const promise = Promise.resolve().then(task);
this.inflight.set(key, promise);
promise
.finally(() => {
if (this.inflight.get(key) === promise) this.inflight.delete(key);
})
.catch(() => {});
return promise;
}
has(key) {
return this.inflight.has(key);
}
}
// 三组以上可直接运行的验证;异步题使用独立执行块。
(async () => {
const group = new Singleflight();
let calls = 0;
let release;
const gate = new Promise((resolve) => {
release = resolve;
});
const task = async () => {
calls += 1;
await gate;
return 42;
};
const a = group.do("x", task);
const b = group.do("x", task);
console.log(a); // 预期:b
console.log(calls); // 预期:0
release();
console.log(await Promise.all([a, b])); // 预期:[42, 42]
console.log(calls); // 预期:1
await Promise.resolve();
console.log(group.has("x")); // 预期:false
const error = new Error("boom");
console.log(await Promise.resolve(group.do("e", () => Promise.reject(error))).then(() => false, () => true)); // 预期:true(拒绝)
await Promise.resolve();
console.log(group.has("e")); // 预期:false
console.log(await group.do("e", () => 7)); // 预期:7
})().catch(console.error);
复杂度分析
时间复杂度:平均 O(1)/次。在途 Promise 按 key 通过 Map 查找、登记和清理。
空间复杂度:O(k)。Map 至多保留 k 个同时在途的不同 key。