在实际开发中,可能会遇到同一个接口被多次调用的情况(例如用户快速点击按钮或页面频繁刷新)。为了避免重复请求浪费资源,可以通过以下设计思路实现取消重复请求。
1.实现思路
-
唯一标识请求
为每个请求生成一个唯一标识符(requestKey),通常由请求的 method 和 url 组成。如果需要更精确,可以将 params 或 data 也纳入标识符。 -
存储请求
使用一个数据结构(如 Map)来存储当前正在进行的请求及其对应的 AbortController 实例。 -
取消重复请求
在发送新请求时,检查 Map 中是否已经存在相同的 requestKey。如果存在,调用 AbortController.abort() 取消上一个请求,并从 Map 中移除。如果不存在,则将当前请求的 AbortController 存入 Map。 -
移除已完成的请求
在请求完成或失败时,从 Map 中移除对应的 requestKey。
2.实现步骤
- 创建 Map 存储请求
使用 Map 数据结构存储请求的唯一标识符和对应的 AbortController。
const pendingRequests = new Map<string, AbortController>();
- 生成请求的唯一标识符
定义一个函数,根据请求的 method 和 url 生成唯一标识符。
const getRequestKey = (config: AxiosRequestConfig): string => {
return `${config.method}:${config.url}`;
};
- 请求拦截器
在请求拦截器中:检查 Map 中是否存在相同的 requestKey。如果存在,取消上一个请求。
如果不存在则为当前请求创建一个新的 AbortController,并存入 Map。
axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => {
const requestKey = getRequestKey(config);
// 如果存在相同的请求,则取消上一次请求
if (pendingRequests.has(requestKey)) {
const controller = pendingRequests.get(requestKey);
controller?.abort(); // 取消上次请求
pendingRequests.delete(requestKey); // 从 Map 中移除
}
// 为当前请求创建一个新的 AbortController
const controller = new AbortController();
config.signal = controller.signal;
// 将当前请求存储到 Map 中
pendingRequests.set(requestKey, controller);
return config;
});
- 响应拦截器
在响应拦截器中:请求完成后,从 Map 中移除对应的 requestKey。
axiosInstance.interceptors.response.use(
(response: AxiosResponse) => {
const requestKey = getRequestKey(response.config);
pendingRequests.delete(requestKey); // 请求完成后移除
return response.data;
},
(error) => {
if (axios.isCancel(error)) {
console.warn("Request canceled:", error.message);
} else {
const requestKey = getRequestKey(error.config);
pendingRequests.delete(requestKey); // 请求完成后移除
console.error("API Error:", error.response?.data || error.message);
}
return Promise.reject(error);
}
);
- 完整代码实现
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
// 用于存储请求的 Map
const pendingRequests = new Map<string, AbortController>();
// 生成请求的唯一标识
const getRequestKey = (config: AxiosRequestConfig): string => {
return `${config.method}:${config.url}`;
};
// 创建 Axios 实例
const axiosInstance = axios.create({
baseURL: import.meta.env.VITE_BASE_URL, // 替换为你的 API 基础 URL
timeout: 50000, // 请求超时时间
headers: {
"Content-Type": "application/json",
},
});
// 请求拦截器
axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => {
const requestKey = getRequestKey(config);
// 如果存在相同的请求,则取消上一次请求
if (pendingRequests.has(requestKey)) {
const controller = pendingRequests.get(requestKey);
controller?.abort(); // 取消上次请求
pendingRequests.delete(requestKey); // 从 Map 中移除
}
// 为当前请求创建一个新的 AbortController
const controller = new AbortController();
config.signal = controller.signal;
// 将当前请求存储到 Map 中
pendingRequests.set(requestKey, controller);
return config;
});
// 响应拦截器
axiosInstance.interceptors.response.use(
(response: AxiosResponse) => {
const requestKey = getRequestKey(response.config);
pendingRequests.delete(requestKey); // 请求完成后移除
return response.data;
},
(error) => {
if (axios.isCancel(error)) {
console.warn("Request canceled:", error.message);
} else {
const requestKey = getRequestKey(error.config);
pendingRequests.delete(requestKey); // 请求完成后移除
console.error("API Error:", error.response?.data || error.message);
}
return Promise.reject(error);
}
);
export default axiosInstance;