在 JavaScript 中,取消请求的方案取决于你使用的请求方式(原生 XMLHttpRequest/fetch API,或第三方库如 axios),核心思路是调用请求对象的「取消接口」或利用「信号机制」中断请求。以下是不同场景的具体实现的方案:
一、原生 XMLHttpRequest(XHR):abort () 方法
XMLHttpRequest 是传统的请求方式,内置 abort() 方法可直接取消未完成的请求,兼容性最好(支持所有浏览器)。
实现步骤:
- 创建
XMLHttpRequest实例; - 调用
open()初始化请求后,可通过abort()取消; - 取消后会触发
abort事件,可在事件回调中处理取消逻辑(如清理状态)。
示例代码:
// 1. 创建 XHR 实例
const xhr = new XMLHttpRequest();
// 2. 监听 abort 事件(请求被取消时触发)
xhr.addEventListener('abort', () => {
console.log('XHR 请求已取消');
});
// 3. 监听错误事件
xhr.addEventListener('error', (err) => {
console.log('XHR 请求失败:', err);
});
// 4. 初始化并发送请求
xhr.open('GET', 'https://api.example.com/data', true);
xhr.send();
// 5. 取消请求(如 1 秒后取消,或用户触发取消按钮)
setTimeout(() => {
xhr.abort(); // 调用 abort() 取消请求
}, 1000);
关键说明:
- 已完成的请求(
readyState === 4)调用abort()无效; - 取消后
xhr.status会变为0,可通过该状态判断请求是否被取消。
二、原生 fetch API:AbortController 信号
fetch API 本身没有 abort() 方法,需通过 AbortController(ES2017 标准)的信号机制取消请求,现代浏览器(Chrome 66+、Firefox 60+、Safari 12.1+)均支持。
实现步骤:
- 创建
AbortController实例,其signal属性作为信号传递给fetch; fetch请求时,通过signal监听取消信号;- 调用
controller.abort()触发取消,fetch会抛出AbortError异常,需通过catch捕获。
示例代码:
// 1. 创建 AbortController 实例
const controller = new AbortController();
const signal = controller.signal; // 取消信号
// 2. 发送 fetch 请求(传入 signal)
fetch('https://api.example.com/data', { signal })
.then((response) => {
if (!response.ok) throw new Error('请求失败');
return response.json();
})
.then((data) => console.log('请求成功:', data))
.catch((err) => {
// 3. 捕获取消异常(区分取消和其他错误)
if (err.name === 'AbortError') {
console.log('fetch 请求已取消');
} else {
console.log('fetch 请求失败:', err);
}
});
// 4. 取消请求(如用户点击取消按钮)
setTimeout(() => {
controller.abort(); // 触发取消信号
}, 1000);
关键说明:
- 同一
signal可关联多个fetch请求,调用abort()会取消所有关联请求; - 必须通过
err.name === 'AbortError'区分 “取消” 和 “网络错误”“请求失败” 等场景。
三、第三方库:axios(CancelToken 或 AbortController)
axios 是前端常用的请求库,提供了两种取消方案:旧版 CancelToken(已废弃) 和 新版 AbortController(推荐,对齐原生 fetch) 。
方案 1:新版 AbortController(推荐,v0.22.0+ 支持)
与原生 fetch 用法一致,axios 支持直接传入 signal 参数:
import axios from 'axios';
// 1. 创建 AbortController 实例
const controller = new AbortController();
const signal = controller.signal;
// 2. 发送 axios 请求(传入 signal)
axios.get('https://api.example.com/data', { signal })
.then((response) => console.log('axios 请求成功:', response.data))
.catch((err) => {
// 3. 捕获取消异常
if (axios.isCancel(err)) {
console.log('axios 请求已取消:', err.message);
} else {
console.log('axios 请求失败:', err);
}
});
// 4. 取消请求(可传入取消原因)
setTimeout(() => {
controller.abort('用户主动取消'); // 取消原因会作为 err.message
}, 1000);
方案 2:旧版 CancelToken(已废弃,不推荐)
axios 旧版通过 CancelToken 取消请求,目前已被 AbortController 替代,仅用于兼容旧代码:
import axios from 'axios';
// 1. 创建 CancelToken 实例
const CancelToken = axios.CancelToken;
let cancel; // 存储取消函数
// 2. 发送请求(通过 cancelToken 配置)
axios.get('https://api.example.com/data', {
cancelToken: new CancelToken((c) => {
cancel = c; // c 是取消函数,赋值给外部变量
})
})
.then((response) => console.log('请求成功:', response.data))
.catch((err) => {
if (axios.isCancel(err)) {
console.log('请求已取消:', err.message);
} else {
console.log('请求失败:', err);
}
});
// 3. 取消请求(调用 cancel 函数,可传入原因)
setTimeout(() => {
cancel('用户主动取消');
}, 1000);
四、常见应用场景:取消重复请求
实际开发中,最常用的场景是「取消重复请求」(如用户快速点击按钮,多次发送同一请求,仅保留最后一次),核心思路是:
- 维护一个「请求缓存池」(对象 / Map),存储当前未完成的请求;
- 发送请求前,检查缓存池:若存在相同请求,先取消旧请求;
- 请求完成(成功 / 失败 / 取消)后,从缓存池移除该请求。
示例(axios + AbortController 实现重复请求取消):
import axios from 'axios';
// 缓存池:key 为请求标识(如 url + 参数),value 为取消函数
const requestCache = new Map();
/**
* 发送请求(自动取消重复请求)
* @param {Object} config - axios 请求配置
* @returns {Promise} 请求结果
*/
function requestWithCancel(config) {
// 1. 生成请求唯一标识(根据 url + method + 参数生成)
const requestKey = `${config.method || 'get'}-${config.url}-${JSON.stringify(config.params || {})}`;
// 2. 若存在重复请求,先取消旧请求
if (requestCache.has(requestKey)) {
const oldController = requestCache.get(requestKey);
oldController.abort('取消重复请求');
requestCache.delete(requestKey); // 移除旧请求缓存
}
// 3. 创建新的 AbortController
const controller = new AbortController();
config.signal = controller.signal; // 绑定信号
// 4. 缓存当前请求的取消控制器
requestCache.set(requestKey, controller);
// 5. 发送请求,完成后移除缓存
return axios(config)
.finally(() => {
// 请求完成(成功/失败/取消),从缓存池移除
if (requestCache.get(requestKey) === controller) {
requestCache.delete(requestKey);
}
});
}
// 测试:快速调用两次相同请求,第一次会被取消
requestWithCancel({ method: 'get', url: 'https://api.example.com/data' });
requestWithCancel({ method: 'get', url: 'https://api.example.com/data' });
五、关键注意事项
-
取消时机:仅能取消「未完成的请求」(处于 pending 状态),已完成(200 OK)或已终止(网络错误)的请求无法取消;
-
服务器端状态:客户端取消请求后,服务器可能仍在处理该请求(只是客户端不再接收响应),若需真正终止服务器逻辑,需配合后端接口(如基于令牌的取消机制);
-
内存泄漏:取消请求后,需清理相关资源(如移除事件监听、删除缓存池中的请求),避免内存泄漏;
-
兼容性:
AbortController:现代浏览器 / Node.js 15+ 支持,IE 不支持;XMLHttpRequest.abort():支持所有浏览器(包括 IE);- 若需兼容 IE,优先使用
XMLHttpRequest或 axios 旧版CancelToken。
总结
| 请求方式 | 取消方案 | 优点 | 缺点 |
|---|---|---|---|
| XMLHttpRequest | xhr.abort() | 兼容性最好(支持 IE) | 语法繁琐,功能较少 |
| fetch API | AbortController | 原生无依赖,语法现代 | 不支持 IE,需捕获 AbortError |
| axios | AbortController(推荐) | 语法简洁,支持拦截器、取消重复请求 | 需引入第三方库 |
| axios | CancelToken(废弃) | 兼容旧代码 | 已被标准废弃,不推荐新使用 |
推荐实践:
- 现代项目(不兼容 IE):优先使用
fetch API + AbortController或axios + AbortController; - 需兼容 IE:使用
XMLHttpRequest; - 实际开发中,结合「请求缓存池」实现重复请求取消,提升用户体验。