以下是一个 生产级 axios 封装方案,包含 请求 / 响应拦截器、统一错误处理、重复请求取消、请求缓存、超时控制 等核心功能,重点解决「取消请求」(重复请求自动取消 + 手动取消)的实际需求:
封装核心思路
-
基础配置:统一 baseURL、超时时间、请求头;
-
拦截器:请求拦截器添加 Token、请求 ID;响应拦截器统一处理成功 / 失败;
-
取消请求核心:
- 用
Map维护「请求缓存池」,存储未完成的请求(key 为请求唯一标识,value 为取消控制器); - 重复请求触发时,先取消旧请求,再发送新请求;
- 支持手动取消单个 / 全部请求;
- 用
-
错误处理:区分「取消错误」「网络错误」「业务错误」(如 401、403);
-
扩展性:支持请求缓存、自定义配置(如是否允许重复请求、是否缓存响应)。
完整封装代码(request.js)
import axios from 'axios';
import { getToken, removeToken, router } from '@/utils/auth'; // 假设的 Token 工具和路由
// 1. 创建 axios 实例
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL, // 环境变量(Vite/Env)
timeout: 10000, // 超时时间 10s
headers: {
'Content-Type': 'application/json;charset=utf-8'
}
});
// 2. 核心:请求缓存池(存储未完成的请求,用于取消重复请求)
/**
* 缓存池结构:Map<requestKey, AbortController>
* requestKey:请求唯一标识(method + url + 序列化参数)
* AbortController:取消控制器(调用 abort() 可取消请求)
*/
const requestCache = new Map();
// 3. 辅助函数:生成请求唯一标识(避免重复请求)
function generateRequestKey(config) {
const { method = 'get', url = '', params = {}, data = {} } = config;
// 序列化参数(确保相同参数顺序生成相同 key)
const paramsStr = JSON.stringify(params);
const dataStr = JSON.stringify(data);
return `${method.toLowerCase()}-${url}-${paramsStr}-${dataStr}`;
}
// 4. 辅助函数:添加请求到缓存池
function addRequestToCache(config) {
const requestKey = generateRequestKey(config);
// 若已存在相同请求,先取消旧请求
if (requestCache.has(requestKey)) {
const oldController = requestCache.get(requestKey);
oldController.abort(`取消重复请求:${requestKey}`);
requestCache.delete(requestKey);
}
// 创建新的取消控制器,绑定到请求配置
const controller = new AbortController();
config.signal = controller.signal;
// 存入缓存池
requestCache.set(requestKey, controller);
return requestKey;
}
// 5. 辅助函数:从缓存池移除请求
function removeRequestFromCache(config) {
const requestKey = generateRequestKey(config);
if (requestCache.has(requestKey)) {
requestCache.delete(requestKey);
}
}
// 6. 手动取消请求的 API(对外暴露)
/**
* 手动取消单个请求
* @param {Object} config - 请求配置(需包含 method、url、params/data)
*/
export function cancelRequest(config) {
const requestKey = generateRequestKey(config);
if (requestCache.has(requestKey)) {
const controller = requestCache.get(requestKey);
controller.abort(`手动取消请求:${requestKey}`);
requestCache.delete(requestKey);
return true;
}
return false; // 未找到对应请求
}
/**
* 手动取消所有未完成的请求
*/
export function cancelAllRequests() {
requestCache.forEach((controller, key) => {
controller.abort(`手动取消所有请求:${key}`);
});
requestCache.clear();
}
// 7. 请求拦截器:添加 Token、处理重复请求
service.interceptors.request.use(
(config) => {
// 忽略重复请求(通过自定义配置 skipDuplicateCancel 控制)
if (!config.skipDuplicateCancel) {
addRequestToCache(config); // 添加到缓存池(自动取消重复请求)
}
// 添加 Token(如 JWT)
const token = getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
// 请求初始化失败(如参数错误),移除缓存
removeRequestFromCache(error.config);
return Promise.reject(error);
}
);
// 8. 响应拦截器:统一处理响应、移除缓存
service.interceptors.response.use(
(response) => {
// 请求完成(成功),从缓存池移除
removeRequestFromCache(response.config);
// 统一响应格式(假设后端返回 { code: 0, data: {}, msg: '' })
const { code, data, msg } = response.data;
if (code === 0) {
// 业务成功:返回数据(支持缓存响应,通过 customCache 控制)
if (response.config.customCache) {
// 这里可添加缓存逻辑(如存入 localStorage/sessionStorage)
}
return data;
} else {
// 业务失败:抛出错误(如 code: 1001 代表参数错误)
console.error(`业务错误 [${code}]:`, msg);
return Promise.reject(new Error(msg || '请求失败'));
}
},
(error) => {
// 请求完成(失败/取消),从缓存池移除
if (error.config) {
removeRequestFromCache(error.config);
}
// 区分错误类型
if (axios.isCancel(error)) {
// 取消错误:不抛出(避免影响全局错误提示),返回自定义取消标识
console.log('请求被取消:', error.message);
return Promise.reject({ type: 'cancel', message: error.message });
} else if (!window.navigator.onLine) {
// 网络错误
console.error('网络错误:请检查网络连接');
return Promise.reject(new Error('网络错误:请检查网络连接'));
} else {
// HTTP 错误(如 401、403、500)
const status = error.response?.status;
switch (status) {
case 401:
// Token 过期/未授权:清除 Token 并跳转登录页
removeToken();
router.push('/login?redirect=' + encodeURIComponent(router.currentRoute.value.fullPath));
return Promise.reject(new Error('登录已过期,请重新登录'));
case 403:
return Promise.reject(new Error('无权限访问,请联系管理员'));
case 500:
return Promise.reject(new Error('服务器错误,请稍后重试'));
default:
const errMsg = error.response?.data?.msg || '请求失败';
return Promise.reject(new Error(errMsg));
}
}
}
);
// 9. 对外暴露核心请求方法(get/post/put/delete)
export const request = {
get(url, params = {}, config = {}) {
return service.get(url, { params, ...config });
},
post(url, data = {}, config = {}) {
return service.post(url, data, config);
},
put(url, data = {}, config = {}) {
return service.put(url, data, config);
},
delete(url, params = {}, config = {}) {
return service.delete(url, { params, ...config });
}
};
// 导出默认请求方法
export default request;
封装功能说明
1. 基础使用(无特殊配置)
import request from '@/utils/request';
// GET 请求
async function fetchData() {
try {
const data = await request.get('/api/data', { id: 1 });
console.log('请求成功:', data);
} catch (err) {
// 区分取消错误和其他错误
if (err.type === 'cancel') {
console.log('请求被取消:', err.message);
} else {
console.error('请求失败:', err.message);
}
}
}
// POST 请求
async function submitData() {
try {
const data = await request.post('/api/submit', { name: '张三' });
console.log('提交成功:', data);
} catch (err) {
console.error('提交失败:', err.message);
}
}
2. 取消请求相关用法
(1)自动取消重复请求(默认开启)
当快速多次调用同一请求(相同 method、url、params/data)时,会自动取消前一次未完成的请求,仅保留最后一次:
// 快速调用 3 次相同请求,前 2 次会被自动取消
fetchData();
fetchData();
fetchData(); // 仅最后一次有效
(2)跳过重复请求取消(自定义配置)
某些场景(如并发提交多个不同参数的请求)需允许重复请求,通过 skipDuplicateCancel: true 关闭自动取消:
// 允许重复请求(即使参数相同,也不取消旧请求)
request.get('/api/data', { id: 1 }, { skipDuplicateCancel: true });
(3)手动取消单个请求
需传入与请求完全一致的配置(method、url、params/data):
import { request, cancelRequest } from '@/utils/request';
// 发送请求
request.get('/api/data', { id: 1 });
// 手动取消该请求(需匹配配置)
setTimeout(() => {
cancelRequest({
method: 'get',
url: '/api/data',
params: { id: 1 }
});
}, 500);
(4)手动取消所有未完成请求
适用于「页面卸载」「退出登录」等场景,取消所有 pending 状态的请求:
import { cancelAllRequests } from '@/utils/request';
// 页面卸载时取消所有请求(React/Vue 生命周期)
// React:useEffect 清理函数
useEffect(() => {
return () => cancelAllRequests();
}, []);
// Vue:onUnmounted
onUnmounted(() => {
cancelAllRequests();
});
3. 其他实用功能
(1)请求缓存(自定义配置)
通过 customCache: true 开启响应缓存(需在响应拦截器中扩展缓存逻辑,如存入本地存储):
// 开启缓存,相同请求会优先读取缓存(需自行实现缓存逻辑)
request.get('/api/data', { id: 1 }, { customCache: true });
(2)超时控制(全局 / 局部)
-
全局超时:在
axios.create中配置timeout: 10000(10 秒); -
局部超时:单个请求覆盖全局配置:
// 该请求超时时间为 5 秒(覆盖全局 10 秒) request.get('/api/slow-data', {}, { timeout: 5000 });
关键细节说明
-
请求唯一标识生成:
- 用
method + url + 序列化参数作为 key,确保相同请求(参数顺序不影响)生成相同 key; - 若请求参数包含非序列化值(如函数),需额外处理(或排除该参数),避免 key 不一致。
- 用
-
取消错误处理:
- 取消错误通过
err.type === 'cancel'区分,避免与业务错误、网络错误混淆; - 取消错误不会触发全局错误提示(如 Toast),提升用户体验。
- 取消错误通过
-
内存泄漏防护:
- 所有请求完成(成功 / 失败 / 取消)后,都会从缓存池移除,避免内存泄漏;
- 页面卸载时调用
cancelAllRequests(),确保销毁未完成请求。
-
兼容性:
- 基于
AbortController实现取消(axios v0.22.0+ 支持),对齐原生 fetch 标准; - 若需兼容旧版 axios(v0.22.0 以下),可替换为
CancelToken(但已废弃,不推荐)。
- 基于
扩展场景(可选)
-
添加请求加载状态:
- 在请求拦截器中触发加载状态(如显示 Loading);
- 在响应拦截器中关闭加载状态(需处理同时多个请求的 Loading 合并)。
-
接口重试机制:
- 对 500、503 等服务器错误,添加自动重试逻辑(通过
config.retry配置重试次数)。
- 对 500、503 等服务器错误,添加自动重试逻辑(通过
-
全局错误提示:
- 结合 UI 库(如 Element Plus、Ant Design),在响应拦截器中统一弹出错误提示(如 ElMessage)。
总结
该封装方案的核心优势:
- 取消请求:自动取消重复请求 + 手动取消单个 / 全部请求,覆盖绝大多数场景;
- 通用性:统一请求 / 响应处理,减少重复代码;
- 扩展性:支持自定义配置(跳过重复取消、缓存、超时),便于后续扩展;
- 稳定性:完善的错误处理和内存泄漏防护,适合生产环境使用。
使用时只需按 request.get/post 调用,无需关心底层取消逻辑,大幅提升开发效率。