文章目录

Axios拦截器:前端通信的"交通警察"🚦
引言:为什么需要拦截器?
想象一下你是一个城市的交通管理员,每天有成千上万的车辆(HTTP请求)在城市(你的应用)中穿梭。如果没有交通信号灯和交警(拦截器),这些车辆会乱成一团:
- 每辆车都要自己找路(每个请求手动处理)
- 遇到事故(错误)没有统一处理
- 特殊车辆(特殊请求)没有优先通道
Axios拦截器就像是这个城市的智能交通管理系统,它能:
- 统一管理所有进出的请求和响应
- 自动处理重复性工作(如添加token)
- 集中处理错误和异常
- 监控和记录请求过程
拦截器的作用与好处
作用概览表
拦截器类型 | 主要作用 | 典型应用场景 |
---|---|---|
请求拦截器 | 在请求发送前处理 | 添加认证token、设置请求头、参数加密 |
响应拦截器 | 在响应返回后处理 | 统一错误处理、数据格式化、响应解密 |
五大核心好处
- 代码复用:避免在每个请求中重复相同代码
- 集中管理:统一处理认证、错误等逻辑
- 流程控制:可以中断或修改请求/响应流程
- 增强可读性:业务代码更专注于业务本身
- 便于维护:修改拦截逻辑只需改一处
拦截器的工作原理
流程图解
[发起请求] →
[请求拦截器] →
[实际发送请求] →
[服务器处理] →
[响应返回] →
[响应拦截器] →
[最终数据处理]
原理详解
Axios内部维护了两个拦截器链(数组):
- 请求拦截器链:按添加顺序执行(先添加先执行)
- 响应拦截器链:按添加逆序执行(先添加后执行)
当发起请求时,Axios会:
- 创建一个Promise链
- 依次执行请求拦截器
- 发送实际HTTP请求
- 依次执行响应拦截器
- 返回最终处理结果
拦截器的多种写法与实践
1. 基础写法
// 添加请求拦截器
axios.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
console.log('请求拦截器 - 处理请求配置', config);
config.headers.Authorization = 'Bearer your_token'; // 添加token
return config; // 必须返回处理后的config
},
function (error) {
// 对请求错误做些什么
console.log('请求拦截器 - 错误处理', error);
return Promise.reject(error);
}
);
// 添加响应拦截器
axios.interceptors.response.use(
function (response) {
// 对响应数据做点什么 (2xx范围内的状态码)
console.log('响应拦截器 - 处理成功响应', response);
return response.data; // 通常我们只需要data部分
},
function (error) {
// 对响应错误做点什么 (超出2xx范围的状态码)
console.log('响应拦截器 - 处理错误响应', error);
if (error.response.status === 401) {
// 处理未授权错误
window.location.href = '/login';
}
return Promise.reject(error);
}
);
2. 类封装写法(更工程化)
class HttpInterceptor {
constructor() {
this.instance = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000
});
this.setupInterceptors();
}
setupInterceptors() {
// 请求拦截
this.instance.interceptors.request.use(
config => {
// 请求前添加loading
this.showLoading();
// 添加认证信息
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => {
this.hideLoading();
return Promise.reject(error);
}
);
// 响应拦截
this.instance.interceptors.response.use(
response => {
this.hideLoading();
// 处理成功响应
if (response.data.code === 200) {
return response.data.data;
} else {
// 业务逻辑错误
this.handleBusinessError(response.data);
return Promise.reject(response.data);
}
},
error => {
this.hideLoading();
// 处理HTTP错误
this.handleHttpError(error);
return Promise.reject(error);
}
);
}
showLoading() { /* 显示加载动画 */ }
hideLoading() { /* 隐藏加载动画 */ }
handleBusinessError(data) {
const errorMap = {
401: '未授权,请重新登录',
403: '拒绝访问',
404: '请求资源不存在',
500: '服务器错误'
};
const message = errorMap[data.code] || '未知错误';
alert(message);
}
handleHttpError(error) {
if (error.response) {
// 服务器返回了响应但状态码不在2xx范围
console.error('响应错误:', error.response.status);
} else if (error.request) {
// 请求已发出但没有收到响应
console.error('无响应:', error.request);
} else {
// 发送请求时出了点问题
console.error('请求错误:', error.message);
}
}
}
// 使用示例
const http = new HttpInterceptor();
http.instance.get('/user/123');
3. 多实例不同拦截策略
// 创建两个不同的axios实例
const publicApi = axios.create({
baseURL: 'https://api.example.com/public'
});
const privateApi = axios.create({
baseURL: 'https://api.example.com/private'
});
// 公共API拦截器 - 简单处理
publicApi.interceptors.response.use(
response => response.data,
error => {
console.error('公共API错误:', error);
return Promise.reject(error);
}
);
// 私有API拦截器 - 复杂处理
privateApi.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (!token) {
throw new Error('无访问令牌');
}
config.headers.Authorization = `Bearer ${token}`;
return config;
});
privateApi.interceptors.response.use(
response => response.data,
error => {
if (error.response.status === 401) {
// token过期,跳转登录
window.location.href = '/login?expired=1';
}
return Promise.reject(error);
}
);
高级应用场景
1. 重试机制
axios.interceptors.response.use(
response => response,
error => {
const config = error.config;
// 设置重试次数,默认为0
config.__retryCount = config.__retryCount || 0;
// 检查是否超过最大重试次数(3次)
if (config.__retryCount >= 3) {
return Promise.reject(error);
}
// 增加重试计数器
config.__retryCount += 1;
// 创建新的Promise来处理指数退避
const delay = new Promise(resolve => {
setTimeout(() => resolve(), 1000 * config.__retryCount);
});
// 返回Promise,在延迟后重试请求
return delay.then(() => axios(config));
}
);
2. 请求取消
// 创建一个Map来存储请求标识和取消函数
const pendingRequests = new Map();
// 生成请求标识
const generateReqKey = (config) => {
const { method, url, params, data } = config;
return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&');
};
// 请求拦截器 - 添加取消逻辑
axios.interceptors.request.use(config => {
const requestKey = generateReqKey(config);
if (pendingRequests.has(requestKey)) {
// 如果请求已存在,取消前一个
pendingRequests.get(requestKey).cancel('重复请求');
}
// 创建取消令牌
config.cancelToken = new axios.CancelToken(cancel => {
pendingRequests.set(requestKey, { cancel });
});
return config;
});
// 响应拦截器 - 移除已完成请求
axios.interceptors.response.use(response => {
const requestKey = generateReqKey(response.config);
pendingRequests.delete(requestKey);
return response;
}, error => {
if (axios.isCancel(error)) {
console.log('请求被取消:', error.message);
return Promise.reject(error);
}
const requestKey = generateReqKey(error.config);
if (error.config) {
pendingRequests.delete(requestKey);
}
return Promise.reject(error);
});
3. 性能监控
// 请求开始时间记录
axios.interceptors.request.use(config => {
config.metadata = { startTime: new Date() };
return config;
});
// 响应时间计算
axios.interceptors.response.use(
response => {
const endTime = new Date();
const duration = endTime - response.config.metadata.startTime;
console.log(`请求 ${response.config.url} 耗时 ${duration}ms`);
// 可以上报到性能监控系统
reportPerformance({
url: response.config.url,
method: response.config.method,
duration: duration,
status: response.status
});
return response;
},
error => {
if (error.config) {
const endTime = new Date();
const duration = endTime - error.config.metadata.startTime;
console.error(`请求 ${error.config.url} 失败,耗时 ${duration}ms`);
reportPerformance({
url: error.config.url,
method: error.config.method,
duration: duration,
status: error.response?.status || 0,
error: error.message
});
}
return Promise.reject(error);
}
);
拦截器最佳实践
1. 组织架构建议
/src
/api
interceptors/
request/
auth.js # 认证处理
loading.js # 加载状态
log.js # 请求日志
response/
error.js # 错误处理
format.js # 数据格式化
index.js # 整合所有拦截器
services/ # 各个API模块
http.js # axios实例创建
2. 代码示例:模块化拦截器
// http.js - 创建axios实例
import axios from 'axios';
const http = axios.create({
baseURL: process.env.VUE_APP_API_BASE_URL,
timeout: 30000
});
export default http;
// interceptors/request/auth.js
export default function addAuthToken(config) {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}
// interceptors/response/error.js
export default function handleError(error) {
if (error.response) {
switch (error.response.status) {
case 400:
console.error('请求错误');
break;
case 401:
console.error('未授权,请重新登录');
break;
case 403:
console.error('拒绝访问');
break;
case 404:
console.error('请求资源不存在');
break;
case 500:
console.error('服务器错误');
break;
default:
console.error(`未知错误: ${error.response.status}`);
}
}
return Promise.reject(error);
}
// interceptors/index.js - 整合拦截器
import http from '../../http';
import addAuthToken from './request/auth';
import handleError from './response/error';
// 请求拦截器
http.interceptors.request.use(addAuthToken);
// 响应拦截器
http.interceptors.response.use(
response => response.data, // 直接返回data
handleError
);
export default http;
3. 注意事项
执行顺序:多个拦截器按添加顺序执行
- 请求拦截器:先添加的先执行
- 响应拦截器:先添加的后执行
错误处理:确保拦截器中错误被正确传递
- 使用
Promise.reject
传递错误 - 不要"吞掉"错误
- 使用
内存泄漏:
- 使用
eject
移除不再需要的拦截器
const interceptor = axios.interceptors.request.use(...); axios.interceptors.request.eject(interceptor);
- 使用
避免副作用:
- 不要在一个拦截器中修改另一个拦截器的行为
- 保持拦截器职责单一
性能考虑:
- 拦截器中的逻辑应尽量轻量
- 避免在拦截器中执行耗时操作
总结:拦截器的核心价值
Axios拦截器就像是你应用HTTP通信的"中间件管道",它提供了统一的入口和出口来处理所有请求和响应。通过合理使用拦截器,你可以:
- 标准化:统一处理认证、错误、loading状态等
- 解耦:将基础设施逻辑与业务逻辑分离
- 增强:添加重试、缓存、监控等高级功能
- 简化:减少业务代码中的重复逻辑
记住,强大的能力伴随着责任,拦截器虽然强大,但也需要合理设计和使用,避免过度复杂化你的请求处理流程。