Axios 的请求取消机制本质上是基于浏览器提供的 AbortController API(早期版本使用CancelToken ,但现已逐渐被 AbortController 取代)。它的核心原理是通过 中断网络请求 来实现取消,但需要明确以下几点关键细节:
Axios 取消请求的原理
1. 浏览器层的信号传递
- AbortController 本质是一个「信号发射器」: 创建 const controller = new AbortController() 时,浏览器会生成一个带有signal属性的对象。 signal是一个AbortSignal对象,它具备监听「终止事件」的能力。
- 将signal绑定到请求:
axios.get('/api', { signal: controller.signal })
此时 Axios 会将这个signal传递给浏览器底层的fetch API 或 XMLHttpRequest(根据适配器类型)。
CancelToken 的兼容方案(旧版):
-
通过 CancelToken.source() 生成一个 source 对象。
-
将 source.token 传递给请求配置。
-
调用 source.cancel() 时,Axios 会抛出 Cancel 错误并中断请求。
2. 中断请求的触发
- 调用controller.abort() :
- 触发signal的abort事件,并标记 signal.aborted 为 true。
- 浏览器底层(如 fetch)会立即 终止与该信号关联的网络活动。
- 具体中断行为:
- 如果请求尚未完成(例如处于 pending 状态),浏览器会强制关闭 TCP 连接。
- 如果响应正在接收中,浏览器会停止读取响应流。
3. 错误反馈机制
- 请求被取消时,Axios 会抛出一个 CanceledError(旧版为 Cancel 错误)。
- 通过 axios.isCancel(error) 可判断是否为取消触发的错误:
.catch(error => {
if (axios.isCancel(error)) {
console.log('请求被主动取消');
}
});
二、为什么“已发出的请求无法取消”?
网络层的不可逆性
-
- 一旦 HTTP 请求的请求体数据已经开始传输,浏览器无法从客户端“撤回”已发送的字节。
- 取消操作只能终止尚未发送的数据或放弃接收响应,但服务器可能已经处理了部分数据。
2. 底层网络行为的限制
- TCP 连接的中断:
- abort() 本质是 强制关闭 TCP 连接,但无法保证服务器已经接收的数据量。
- 如果数据仍在传输中,浏览器会触发 ECONNABORTED 错误,但服务器可能已接收部分内容。
- UDP 无连接特性(不适用于 HTTP):
- HTTP 基于 TCP,而 TCP 是面向连接的可靠协议,中断后无法恢复。
3. 大文件上传的特殊性
- 如果使用单次请求上传整个大文件,请求一旦发出,客户端只能断开连接,但服务器可能已经接收了部分内容。
- 对于分块上传(chunked upload),可以取消尚未发送的分块,但已成功上传的分块无法撤回。
三、如何正确实现「可取消的大文件上传」
方案一:分块上传(Chunked Upload)
- 实现步骤:
- 优点:
-
- 已上传的分块可保留在服务器(结合后端支持断点续传)。
- 未上传的分块可立即终止。
方案二:后端事务性处理
- 流程设计:
- 前端开始上传时,先请求后端生成一个「临时上传会话 ID」。
- 上传过程中,携带该 ID 标识当前上传任务。
- 取消时,调用后端 API 通知删除与该 ID 关联的临时数据。
- 优点:
- 彻底清理服务器上的残留数据。
- 避免存储无效的上传内容。
代码示例:分块上传 + 动态取消
// 分块上传管理器
class ChunkedUploader {
constructor(file, chunkSize = 1024 * 1024) {
this.file = file;
this.chunkSize = chunkSize;
this.chunks = [];
this.currentChunk = 0;
this.abortControllers = [];
this.uploadSessionId = null;
}
// 初始化分块
prepareChunks() {
const totalChunks = Math.ceil(this.file.size / this.chunkSize);
for (let i = 0; i < totalChunks; i++) {
const start = i * this.chunkSize;
const end = Math.min(start + this.chunkSize, this.file.size);
this.chunks.push(this.file.slice(start, end));
}
}
// 上传单个分块
async uploadChunk(chunk) {
const controller = new AbortController();
this.abortControllers.push(controller);
await axios.post('/upload-chunk', {
chunk,
sessionId: this.uploadSessionId,
index: this.currentChunk,
}, {
signal: controller.signal,
});
this.currentChunk++;
}
// 开始上传
async start() {
const { data } = await axios.post('/create-upload-session');
this.uploadSessionId = data.sessionId;
this.prepareChunks();
for (const chunk of this.chunks) {
await this.uploadChunk(chunk);
}
// 所有分块上传完成后通知后端合并
await axios.post('/complete-upload', { sessionId: this.uploadSessionId });
}
// 取消上传
cancel() {
this.abortControllers.forEach(controller => controller.abort());
axios.post('/cancel-upload', { sessionId: this.uploadSessionId });
}
}
// 使用示例
const uploader = new ChunkedUploader(file);
uploader.start();
// 点击取消按钮
document.getElementById('cancel').addEventListener('click', () => {
uploader.cancel();
});
四、注意事项
- 取消的局限性:
- 只能取消尚未完成的请求,无法回滚已到达服务器的数据。
- 如果请求已处于pending 状态但未开始传输数据(如 DNS 解析阶段),可成功取消。
- 错误处理:
- 使用 axios.isCancel(error) 判断错误是否由取消触发。
- 性能优化:
- 对于大文件上传,分块+并行上传+进度监控是最佳实践。
关键总结
- Axios 取消的本质:通过浏览器 API 强制中断网络连接,但对已传输的数据无控制权。
- 大文件上传的解决方向:
- 分块上传:细粒度控制每个分块的取消。
- 后端协作:清理临时数据,支持断点续传。
- 实际开发建议:
- 优先使用 AbortController(现代浏览器支持)。
- 对旧版浏览器,回退到 CancelToken(需注意 Axios 版本兼容性)。