在 javascript 中实现请求取消的代理模式,核心思路是拦截 `fetch` 或 `xmlhttprequest` 调用,通过一个共享的 `abortcontroller` 实例来管理生命周期。当需要取消时,调用 `abort()` 方法触发 `abortsignal`,使挂起的请求立即抛出错误并终止网络传输。

如何封装支持取消的 fetch 代理
直接回答:创建一个高阶函数或类作为代理,内部持有 `AbortController`,将 `signal` 注入到 `fetch` 配置中。这样调用者只需关注业务逻辑,取消操作由代理统一处理。

class FetchProxy {
constructor() {
this.controller = new AbortController();
}
async request(url, options = {}) {
// 合并用户传入的 signal 与代理自带的 signal
const combinedSignal = options.signal || this.controller.signal;
try {
const response = await fetch(url, { ...options, signal: combinedSignal });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request cancelled');
return null; // 或者返回特定状态码
}
throw error;
}
}
cancel() {
this.controller.abort();
// 重置控制器以便后续新请求可以使用
this.controller = new AbortController();
}
}
// 使用示例
const proxy = new FetchProxy();
proxy.request('https://api.example.com/data').then(console.log);
// 当需要取消时:
proxy.cancel();
上述代码展示了基本的代理结构。关键点在于 `AbortController` 的复用与重置。每次调用 `cancel()` 后,必须创建新的 `controller` 实例,否则后续的请求也会因为旧的 `signal` 处于 abort 状态而立即失败。这种设计确保了代理的生命周期与请求流解耦。
如何处理并发请求的独立取消
直接回答:如果需要在同一代理实例下管理多个独立的请求(例如列表页的多个项),不能共享同一个 `AbortController`。应改为为每个请求生成独立的控制器,并通过 Map 或数组进行追踪。取消某个特定请求时,只针对该请求的控制器调用 `abort()`。

class ConcurrentFetchProxy {
constructor() {
this.activeRequests = new Map();
this.idCounter = 0;
}
async request(id, url, options = {}) {
const controller = new AbortController();
this.activeRequests.set(id, controller);
try {
const response = await fetch(url, { ...options, signal: controller.signal });
if (!response.ok) throw new Error(`Status: ${response.status}`);
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log(`Request ${id} cancelled`);
return null;
}
throw error;
} finally {
// 请求结束(成功、失败或取消)后清理引用
this.activeRequests.delete(id);
}
}
cancelById(id) {
const controller = this.activeRequests.get(id);
if (controller) {
controller.abort();
}
}
cancelAll() {
for (const [id, controller] of this.activeRequests) {
controller.abort();
}
this.activeRequests.clear();
}
}
// 使用示例
const proxy = new ConcurrentFetchProxy();
proxy.request(1, '/api/item/1');
proxy.request(2, '/api/item/2');
// 单独取消 ID 为 1 的请求
proxy.cancelById(1);
这个并发代理模式解决了单一控制器无法区分不同请求的问题。通过 `Map` 存储 `id` 到 `controller` 的映射,实现了细粒度的控制。`finally` 块确保无论请求结果如何,内存中的引用都会被清理,防止内存泄漏。这种模式适用于动态加载内容较多的场景,如无限滚动列表。
基于 AbortSignal 的超时自动取消机制
直接回答:利用 `AbortSignal.timeout()` (现代浏览器支持) 或手动设置定时器结合 `setTimeout` 来实现自动取消。这解决了请求挂起不返回的问题,提升了用户体验和系统稳定性。
// 现代浏览器原生支持 timeout
async function requestWithTimeout(url, timeoutMs) {
const signal = AbortSignal.timeout(timeoutMs);
try {
const res = await fetch(url, { signal });
return await res.json();
} catch (err) {
if (err.name === 'TimeoutError' || err.name === 'AbortError') {
console.log('Request timed out');
return null;
}
throw err;
}
}
当请求超过指定时间,`AbortSignal` 会自动触发 abort 事件,导致 `fetch` 抛出 `TimeoutError`。开发者无需手动管理定时器,代码更简洁。这是处理网络不稳定环境下的最佳实践之一,避免了僵尸请求占用资源。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











