在现代 Web 开发中,请求取消能力已成为构建高性能应用的关键技术。Axios 提供了完善的请求取消机制,这在处理复杂的前端交互场景中尤为重要。

本文将从底层原理出发,深度解析 Axios 的两种取消方案,并提供生产环境的最佳实践指南。

技术实现方案对比

1. 传统 CancelToken 方案

适用版本: Axios < 0.22
实现原理: 基于发布-订阅模式的令牌机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建令牌源
const source = axios.CancelToken.source()

// 发送请求
axios.get('/api', {
cancelToken: source.token
}).catch(err => {
if (axios.isCancel(err)) {
console.log('取消原因:', err.message)
}
})

// 取消请求
source.cancel('用户主动取消')

技术特点

  • 令牌实例维护独立 Promise 状态
  • 取消时触发 Promise 链式反应
  • 需要手动管理引用关系

2. 现代 AbortController 方案

适用版本: Axios >= 0.22
实现原理: 基于 WHATWG 标准接口

1
2
3
4
5
6
7
8
9
10
11
12
const controller = new AbortController()

axios.get('/api', {
signal: controller.signal
}).catch(err => {
if (err.name === 'CanceledError') {
console.log('请求终止')
}
})

// 终止请求
controller.abort()

技术特点

  • 原生支持 EventTarget 事件机制
  • 与 Fetch API 共享中断逻辑
  • 自动垃圾回收机制

核心机制解析

1. 中断时机与效果

阶段 行为表现 网络影响
请求未发出 阻止请求发送 无网络流量
请求已发送未响应 标记为取消状态 响应数据被丢弃
请求已完成 不产生影响 正常处理响应

2. 内存管理机制

CancelToken 潜在风险

1
2
3
4
5
6
7
8
// 错误示例:未清理的令牌引用
const tokens = new Map()

function createRequest() {
const source = axios.CancelToken.source()
tokens.set('key', source)
// 忘记删除将导致内存泄漏
}

AbortController 优化方案

1
2
3
4
5
6
7
8
// 自动释放机制
const controller = new AbortController()
const { signal } = controller

// 请求完成后自动断开连接
signal.addEventListener('abort', () => {
controller = null
}, { once: true })

生产环境最佳实践

以下是 Axios 请求取消机制在各场景下的实践。

1. 请求去重封装

业务场景:在复杂表单提交、实时搜索等高频触发请求的场景中,需要防止重复请求造成的资源浪费和状态混乱。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 智能请求封装:自动取消相同请求
* @param {Object} config - Axios请求配置
* @returns {Promise} 带取消控制的请求实例
*/
const requestPool = new Map(); // 使用Map保存活跃请求控制器

function smartRequest(config) {
// 生成唯一请求标识:方法+URL+参数哈希
const paramHash = JSON.stringify(config.params || config.data);
const requestKey = `${config.method}-${config.url}-${paramHash}`;

// 终止重复请求
if (requestPool.has(requestKey)) {
const oldController = requestPool.get(requestKey);
oldController.abort(`取消重复请求: ${requestKey}`); // 显式传递取消原因
requestPool.delete(requestKey); // 清理旧请求记录
}

// 创建新控制器
const controller = new AbortController();
requestPool.set(requestKey, controller); // 注册到请求池

// 发起请求并自动清理
return axios({
...config,
signal: controller.signal
})
.finally(() => {
// 无论成功失败都清理请求记录
requestPool.delete(requestKey);
})
.catch(err => {
if (err.name === 'CanceledError') {
console.warn('请求被取消:', err.message);
}
throw err;
});
}

关键技术点

  1. 请求指纹算法:通过方法、URL和参数生成唯一标识
  2. 双保险机制:新请求触发旧请求终止,保证唯一性
  3. 自动垃圾回收:finally阶段清理请求记录,防止内存泄漏

2. 竞态条件处理(版本控制)

典型场景:分页快速切换、标签页切换时,确保最终呈现的数据与最后一次操作一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let requestVersion = 0; // 全局请求版本计数器

async function fetchData(params) {
const currentVersion = ++requestVersion; // 原子操作递增版本号

try {
const response = await axios.get('/api/data', {
params,
signal: new AbortController().signal
});

// 版本校验(关键防御逻辑)
if (currentVersion !== requestVersion) {
console.log('过期响应已忽略');
return null; // 主动丢弃过期结果
}

return response.data;
} catch (err) {
if (err.name === 'CanceledError') {
console.log('请求被新版本覆盖');
return null;
}
throw err;
}
}

防御策略

  1. 原子版本号:通过闭包维护全局版本状态
  2. 双保险机制:AbortController + 版本号校验
  3. 过期响应拦截:在业务逻辑层进行二次验证

性能优化策略进阶指南

1. 智能请求调度器(并发控制)

技术痛点:在资源受限环境下(如移动端),需要防止并发请求过多导致的性能问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class RequestScheduler {
constructor(maxConcurrent = 3) {
this.queue = [];
this.activeCount = 0;
this.maxConcurrent = maxConcurrent;
}

add(requestFn) {
return new Promise((resolve, reject) => {
// 封装请求以控制执行时机
const execute = () => {
this.activeCount++;
requestFn()
.then(resolve)
.catch(reject)
.finally(() => {
this.activeCount--;
this.runNext();
});
};

// 队列管理
if (this.activeCount < this.maxConcurrent) {
execute();
} else {
this.queue.push(execute);
}
});
}

runNext() {
if (this.queue.length > 0 && this.activeCount < this.maxConcurrent) {
const next = this.queue.shift();
next();
}
}
}

const scheduler = new RequestScheduler(3);

async function controlledRequest(url) {
return scheduler.add(() => axios.get(url));
}

2. 自适应超时控制(动态计算)

算法原理:基于历史响应时间动态调整超时阈值,实现最优超时配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const responseTimeStats = {
sum: 0,
count: 0,
get average() {
return this.count > 0 ? this.sum / this.count : 1000;
}
};

function adaptiveTimeoutRequest(config) {
const baseTimeout = responseTimeStats.average * 2; // 基线为平均2倍
const controller = new AbortController();

// 超时计时器
const timeoutId = setTimeout(() => {
controller.abort(`自适应超时触发 (${baseTimeout}ms)`);
}, baseTimeout);

return axios({
...config,
signal: controller.signal
})
.then(response => {
// 记录成功响应时间
const latency = performance.now() - startTime;
updateResponseStats(latency);
return response;
})
.catch(err => {
if (err.name === 'CanceledError') {
handleTimeoutError(baseTimeout);
}
throw err;
})
.finally(() => {
clearTimeout(timeoutId);
});
}

function updateResponseStats(latency) {
responseTimeStats.sum += latency;
responseTimeStats.count++;
// 指数平滑滤波
responseTimeStats.average =
0.2 * latency + 0.8 * responseTimeStats.average;
}

动态调整策略

  1. 初始超时:1000ms
  2. 成功响应:新超时 = 历史平均 × 2
  3. 超时发生:自动扩容超时阈值 50%
  4. 定期衰减:每小时重置统计防止数据过载

Happy Coding!