编码母题 / P0 / 前端与专项架构 / 04
只提交最新请求的资源加载器
先读清题意与边界,独立尝试,再展开思路并对照可运行的参考代码。
重要程度必练难度中等预计完成30–40 分钟
题目是什么
参数变化时启动新请求并取消旧请求。旧请求即使不响应取消而晚到,也不能覆盖界面;手动 cancel 后同样不得提交。
输入输出示例
搜索词从 a 变成 ab,a 的响应晚于 ab 到达时,页面仍只显示 ab 的结果。
练习起手式与验证
复制代码后补全空方法,再运行下方三组验证;复杂题所需的辅助类型与输入准备一并放在代码块中。
/*
* 题目:只提交最新请求的资源加载器
* 要求:参数变化时启动新请求并取消旧请求。旧请求即使不响应取消而晚到,也不能覆盖界面;手动
* cancel 后同样不得提交。
* 示例:搜索词从 a 变成 ab,a 的响应晚于 ab 到达时,页面仍只显示 ab 的结果。
*/
class LatestRequest {
constructor(loader) {}
cancel() {}
async run(input, onSuccess) {}
}
(async () => {
const pending = new Map();
const signals = new Map();
const committed = [];
const latest = new LatestRequest((key, { signal }) => {
signals.set(key, signal);
return new Promise((resolve, reject) => pending.set(key, { resolve, reject }));
});
const first = latest.run("old", (value) => committed.push(value));
const second = latest.run("new", (value) => committed.push(value));
console.log(signals.get("old").aborted); // 预期:true
pending.get("new").resolve("new result");
console.log(await second); // 预期:{ status: "committed", value: "new result" }
pending.get("old").resolve("old result");
console.log(await first); // 预期:{ status: "stale" }
})().catch(console.error);
/*
* 题目:只提交最新请求的资源加载器
* 要求:参数变化时启动新请求并取消旧请求。旧请求即使不响应取消而晚到,也不能覆盖界面;手动
* cancel 后同样不得提交。
* 示例:搜索词从 a 变成 ab,a 的响应晚于 ab 到达时,页面仍只显示 ab 的结果。
*/
class LatestRequest {
constructor(loader) {}
cancel() {}
async run(input, onSuccess) {}
}
(async () => {
const pending = new Map();
const signals = new Map();
const committed = [];
const latest = new LatestRequest((key, { signal }) => {
signals.set(key, signal);
return new Promise((resolve, reject) => pending.set(key, { resolve, reject }));
});
const first = latest.run("old", (value) => committed.push(value));
const second = latest.run("new", (value) => committed.push(value));
console.log(signals.get("old").aborted); // 预期:true
pending.get("new").resolve("new result");
console.log(await second); // 预期:{ status: "committed", value: "new result" }
pending.get("old").resolve("old result");
console.log(await first); // 预期:{ status: "stale" }
})().catch(console.error);解题思路
展开思路、不变量与常见边界
关键切入点:AbortSignal 尽力停止底层工作;递增版本号才是晚到结果能否提交的最终判断。
核心模型:每轮分配递增版本号。AbortSignal 尽力停止底层工作,版本号决定 结果是否仍有提交资格;两者不能互相替代,因为 loader 可能忽略取消信号。 不变量:只有当前版本且未取消的成功结果调用 onSuccess;过期成功或失败均 返回 {status:"stale"};当前失败向调用方抛出。每次请求 O(1) 额外状态。 迁移:搜索建议、路由切换、分页筛选和组件卸载。缓存与请求去重是独立语义。
参考代码与验证
“关键机制”注释用于解释实现,下载链接提供未经页面注释修改的原始 JS 文件。
// 关键机制:AbortSignal 尽力停止底层工作;递增版本号才是晚到结果能否提交的最终判断。
class LatestRequest {
constructor(loader) {
if (typeof loader !== "function") throw new TypeError("loader must be a function");
this.loader = loader;
this.version = 0;
this.controller = null;
}
cancel() {
this.version += 1;
this.controller?.abort();
this.controller = null;
}
async run(input, onSuccess) {
if (typeof onSuccess !== "function")
throw new TypeError("onSuccess must be a function");
this.cancel();
const version = this.version;
const controller = new AbortController();
this.controller = controller;
try {
const value = await this.loader(input, { signal: controller.signal });
if (version !== this.version || controller.signal.aborted)
return { status: "stale" };
onSuccess(value);
return { status: "committed", value };
} catch (error) {
if (version !== this.version || controller.signal.aborted)
return { status: "stale" };
throw error;
} finally {
if (this.controller === controller) this.controller = null;
}
}
}
// 三组以上可直接运行的验证;异步题使用独立执行块。
(async () => {
const pending = new Map();
const signals = new Map();
const committed = [];
const latest = new LatestRequest((key, { signal }) => {
signals.set(key, signal);
return new Promise((resolve, reject) => pending.set(key, { resolve, reject }));
});
const first = latest.run("old", (value) => committed.push(value));
const second = latest.run("new", (value) => committed.push(value));
console.log(signals.get("old").aborted); // 预期:true
pending.get("new").resolve("new result");
console.log(await second); // 预期:{ status: "committed", value: "new result" }
pending.get("old").resolve("old result");
console.log(await first); // 预期:{ status: "stale" }
console.log(committed); // 预期:["new result"]
const cancelled = latest.run("cancel", (value) => committed.push(value));
latest.cancel();
pending.get("cancel").reject(new Error("aborted"));
console.log(await cancelled); // 预期:{ status: "stale" }
console.log(committed); // 预期:["new result"]
const failure = latest.run("fail", () => {});
pending.get("fail").reject(new Error("current failure"));
console.log(await Promise.resolve(failure).then(() => false, () => true)); // 预期:true(拒绝)
})().catch(console.error);
// 关键机制:AbortSignal 尽力停止底层工作;递增版本号才是晚到结果能否提交的最终判断。
class LatestRequest {
constructor(loader) {
if (typeof loader !== "function") throw new TypeError("loader must be a function");
this.loader = loader;
this.version = 0;
this.controller = null;
}
cancel() {
this.version += 1;
this.controller?.abort();
this.controller = null;
}
async run(input, onSuccess) {
if (typeof onSuccess !== "function")
throw new TypeError("onSuccess must be a function");
this.cancel();
const version = this.version;
const controller = new AbortController();
this.controller = controller;
try {
const value = await this.loader(input, { signal: controller.signal });
if (version !== this.version || controller.signal.aborted)
return { status: "stale" };
onSuccess(value);
return { status: "committed", value };
} catch (error) {
if (version !== this.version || controller.signal.aborted)
return { status: "stale" };
throw error;
} finally {
if (this.controller === controller) this.controller = null;
}
}
}
// 三组以上可直接运行的验证;异步题使用独立执行块。
(async () => {
const pending = new Map();
const signals = new Map();
const committed = [];
const latest = new LatestRequest((key, { signal }) => {
signals.set(key, signal);
return new Promise((resolve, reject) => pending.set(key, { resolve, reject }));
});
const first = latest.run("old", (value) => committed.push(value));
const second = latest.run("new", (value) => committed.push(value));
console.log(signals.get("old").aborted); // 预期:true
pending.get("new").resolve("new result");
console.log(await second); // 预期:{ status: "committed", value: "new result" }
pending.get("old").resolve("old result");
console.log(await first); // 预期:{ status: "stale" }
console.log(committed); // 预期:["new result"]
const cancelled = latest.run("cancel", (value) => committed.push(value));
latest.cancel();
pending.get("cancel").reject(new Error("aborted"));
console.log(await cancelled); // 预期:{ status: "stale" }
console.log(committed); // 预期:["new result"]
const failure = latest.run("fail", () => {});
pending.get("fail").reject(new Error("current failure"));
console.log(await Promise.resolve(failure).then(() => false, () => true)); // 预期:true(拒绝)
})().catch(console.error);
复杂度分析
时间复杂度:O(1)/请求,不计网络。每次启动只更新版本号、取消旧控制器并登记回调。
空间复杂度:O(1) 内部状态。实例只保留当前版本和当前控制器;旧 Promise 由调用方持有。