在现代前端开发中,网络请求是不可或缺的一部分。Axios 是一个基于 Promise 的 HTTP 客户端,广泛应用于浏览器和 Node.js 环境中。然而,在某些场景下,我们可能需要取消正在进行的请求,例如用户在请求完成前跳转到其他页面,或者重复触发相同的请求时取消之前的请求。本文将深入探讨 Axios 请求取消的原理,并通过一个二次封装的例子来演示如何实现请求取消。
1. 请求取消的原理
Axios 的请求取消功能依赖于 CancelToken
。CancelToken
是一个用于取消请求的令牌,它可以通过 CancelToken.source()
方法创建。每个 CancelToken
实例都有一个 token
和一个 cancel
方法。当调用 cancel
方法时,与该 token
关联的请求将被取消。
1.1 CancelToken 的工作原理
- 创建 CancelToken : 通过
CancelToken.source()
方法创建一个CancelToken
实例,该实例包含一个token
和一个cancel
方法。 - 关联请求 : 在发起请求时,将
token
传递给 Axios 请求配置中的cancelToken
字段。 - 取消请求 : 当需要取消请求时,调用
cancel
方法,Axios 会中断与该token
关联的请求。
1.2 取消请求的流程
- 用户触发某个操作,发起一个请求。
- 在请求完成之前,用户触发了另一个操作,需要取消之前的请求。
- 调用
cancel
方法,Axios 中断之前的请求。 - 发起新的请求。
基于二次封装的 Axios 请求取消详解
为了更好地管理请求取消逻辑,我们可以对 Axios 进行二次封装。以下是一个简单的封装示例,展示了如何在封装中实现请求取消功能。
1. 代码结构分析
1.1 Axios 实例的创建
typescript
const instance: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
baseURL
: 从环境变量中获取 API 的基础 URL。timeout
: 设置请求超时时间为 10 秒。headers
: 设置默认请求头为application/json
。
通过 axios.create
创建了一个 Axios 实例 instance
,后续的所有请求都将基于这个实例。
1.2 取消令牌的管理
typescript
const cancelTokenMap = new Map<string, CancelTokenSource>();
cancelTokenMap
: 使用Map
数据结构来存储每个请求的取消令牌。键是请求的 URL,值是对应的CancelTokenSource
。- 作用: 通过 URL 快速查找和取消对应的请求。
1.3 请求拦截器
typescript
instance.interceptors.request.use(
(config) => {
// 添加 token 到请求头
const token = localStorage.getItem('token');
if (token) {
config.headers = config.headers || {};
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
- 功能 : 在请求发送前,检查本地存储中是否存在
token
,如果存在则将其添加到请求头中。 - 作用: 实现全局的请求头管理,例如身份验证。
1.4 响应拦截器
typescript
instance.interceptors.response.use(
(response: AxiosResponse) => {
// 清理已完成的请求记录
const url = response.config.url;
if (url && cancelTokenMap.has(url)) {
cancelTokenMap.delete(url);
}
// 处理全局响应逻辑
if (response.data.code !== 0) {
return Promise.reject(response.data);
}
return response.data;
},
(error) => {
// 清理失败的请求记录
const url = error.config?.url;
if (url && cancelTokenMap.has(url)) {
cancelTokenMap.delete(url);
}
// 处理全局错误
if (error.response?.status === 401) {
// 处理未授权
window.location.href = '/login';
}
return Promise.reject(error);
}
);
- 功能 :
- 在请求成功时,清理
cancelTokenMap
中对应的请求记录。 - 在请求失败时,清理
cancelTokenMap
中对应的请求记录,并根据状态码处理全局错误(例如未授权时跳转到登录页)。
- 在请求成功时,清理
- 作用: 实现全局的响应和错误处理逻辑。
1.5 封装的请求方法
typescript
const http = {
get: <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {
const source = axios.CancelToken.source();
cancelTokenMap.set(url, source);
return instance.get(url, { ...config, cancelToken: source.token });
},
post: <T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> => {
const source = axios.CancelToken.source();
cancelTokenMap.set(url, source);
return instance.post(url, data, { ...config, cancelToken: source.token });
},
put: <T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> => {
const source = axios.CancelToken.source();
cancelTokenMap.set(url, source);
return instance.put(url, data, { ...config, cancelToken: source.token });
},
delete: <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {
const source = axios.CancelToken.source();
cancelTokenMap.set(url, source);
return instance.delete(url, { ...config, cancelToken: source.token });
},
// 取消指定请求
cancelRequest: (url: string) => {
const source = cancelTokenMap.get(url);
if (source) {
source.cancel(`Request canceled: ${url}`);
cancelTokenMap.delete(url);
}
},
// 取消所有请求
cancelAllRequests: () => {
cancelTokenMap.forEach((source, url) => {
source.cancel(`Request canceled: ${url}`);
cancelTokenMap.delete(url);
});
},
};
- 功能 :
- 封装了
get
、post
、put
、delete
方法,每个方法都会为请求创建一个CancelToken
,并将其存储到cancelTokenMap
中。 - 提供了
cancelRequest
和cancelAllRequests
方法,用于取消指定请求或所有请求。
- 封装了
- 作用: 简化请求调用,并提供灵活的请求取消功能。
2. 请求取消的实现原理
2.1 CancelToken 的作用
CancelToken.source()
: 创建一个CancelTokenSource
对象,包含token
和cancel
方法。token
: 用于关联请求。cancel
: 用于取消请求。
2.2 请求取消的流程
- 发起请求时,创建一个
CancelTokenSource
,并将其存储到cancelTokenMap
中。 - 如果需要取消请求,调用
cancelRequest
或cancelAllRequests
方法。 - 调用
cancel
方法后,Axios 会中断与该token
关联的请求,并抛出一个Cancel
错误。 - 在响应拦截器中,清理已完成的请求记录。
3. 使用场景
3.1 取消重复请求
当用户快速点击按钮多次触发相同的请求时,可以通过 cancelRequest
方法取消之前的请求,只保留最后一次请求。
typescript
http.get('/api/data')
.then((data) => console.log(data))
.catch((error) => {
if (axios.isCancel(error)) {
console.log('Request canceled:', error.message);
} else {
console.error('Error:', error);
}
});
// 取消之前的请求
http.cancelRequest('/api/data');
3.2 页面跳转时取消请求
当用户跳转到其他页面时,可以通过 cancelAllRequests
方法取消所有未完成的请求,避免无效请求占用资源。
typescript
window.addEventListener('beforeunload', () => {
http.cancelAllRequests();
});
完整代码
typescript
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, CancelTokenSource } from 'axios';
// 创建axios实例
const instance: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// 创建一个Map来存储取消令牌
const cancelTokenMap = new Map<string, CancelTokenSource>();
// 请求拦截器
instance.interceptors.request.use(
(config) => {
// 在这里可以添加token等全局请求头
const token = localStorage.getItem('token');
if (token) {
config.headers = config.headers || {};
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
instance.interceptors.response.use(
(response: AxiosResponse) => {
// 清理已完成的请求记录
const url = response.config.url;
if (url && cancelTokenMap.has(url)) {
cancelTokenMap.delete(url);
}
// 在这里处理全局响应逻辑
if (response.data.code !== 0) {
return Promise.reject(response.data);
}
return response.data;
},
(error) => {
// 清理失败的请求记录
const url = error.config?.url;
if (url && cancelTokenMap.has(url)) {
cancelTokenMap.delete(url);
}
// 在这里处理全局错误
if (error.response?.status === 401) {
// 处理未授权
window.location.href = '/login';
}
return Promise.reject(error);
}
);
// 封装请求方法
const http = {
get: <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {
const source = axios.CancelToken.source();
cancelTokenMap.set(url, source);
return instance.get(url, { ...config, cancelToken: source.token });
},
post: <T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> => {
const source = axios.CancelToken.source();
cancelTokenMap.set(url, source);
return instance.post(url, data, { ...config, cancelToken: source.token });
},
put: <T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> => {
const source = axios.CancelToken.source();
cancelTokenMap.set(url, source);
return instance.put(url, data, { ...config, cancelToken: source.token });
},
delete: <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {
const source = axios.CancelToken.source();
cancelTokenMap.set(url, source);
return instance.delete(url, { ...config, cancelToken: source.token });
},
// 添加取消请求的方法
// http.cancelRequest('/api/some-endpoint');
cancelRequest: (url: string) => {
const source = cancelTokenMap.get(url);
if (source) {
source.cancel(`Request canceled: ${url}`);
cancelTokenMap.delete(url);
}
},
// 取消所有请求
cancelAllRequests: () => {
cancelTokenMap.forEach((source, url) => {
source.cancel(`Request canceled: ${url}`);
cancelTokenMap.delete(url);
});
},
};
export default http;
4. 总结
通过二次封装 Axios,我们实现了一个功能强大且易于使用的 HTTP 客户端。它不仅支持全局的请求和响应拦截,还提供了灵活的请求取消功能,适用于多种场景。希望本文能帮助你更好地理解和使用 Axios 的请求取消功能。