Axios 错误处理的设计与进阶封装,实现网络层面数据与状态解耦

有这样一句话:在前端项目中,网络请求失败不是异常,而是常态。真正拉开项目工程质量差距的,不是会不会用 axios,而是如何设计一套可维护、可扩展、可协作的网络错误处理体系。成熟的项目组有现成可用的 axios 网络封装设计,不成熟的项目组网络错误处理原始而杂乱,很多开发者在成熟的项目组开发了多年,依然不了解 Axios 错误处理的设计封装,只处在知道有这个东西,而不知道如何设计的状态下。本文围绕 Axios 的拦截器机制,系统性分析可配置、可分级、可扩展的网络请求实战封装策略。
一、为什么网络错误处理一定要下沉到 Axios 层
在项目中,如果常规错误处理放在业务层,就会需要给每个 async/await 都要写一段 try-catch,同一种错误(如 token 过期)被处理 N 次,UI 提示风格难以统一,后续想要改动极其痛苦:
// 典型的业务层污染
async function loadList() {
try {
const res = await getList();
list.value = res.data;
} catch (e) {
ElMessage.error('请求失败');
}
}
这肯定是不可取的,应该将错误分级并合并统一处理。对于网络错误的判断逻辑、分类、兜底策略,本就应该属于请求基础设施层。
二、Axios 拦截器 interceptors
Axios 提供了两个关键拦截器,分别是请求拦截器和响应拦截器,可以用来行使不同的职责。

1、拦截器的基础应用
网络请求一般有两大类失败,其一是 HTTP / 网络层错误,比如断网、请求超时、上游错误(500/502/503)等。其二是业务层错误,比如 code !== 0(响应状态码是 200,但业务状态异常)、token 过期、权限问题等。
// src/utils/api.js
import axios from 'axios';
import { ElMessage } from 'element-plus';
// 创建 axios 实例
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: 10000,
});
// 请求拦截器(可选:添加 token 等)
request.interceptors.request.use(
(config) => {
// 例如:从 localStorage 获取 token 并添加到请求头
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截是集中处理错误的核心
request.interceptors.response.use(
(response) => {
const { code, message, data } = response.data;
if (code !== 0) {
return Promise.reject({ type: 'business', code, message });
}
return data;
},
(error) => {
const { response } = error;
if (response) {
// 有响应(HTTP 状态码 4xx / 5xx)
switch (response.status) {
case 400:
ElMessage.error('请求参数错误');
break;
case 401:
ElMessage.error('未授权,请重新登录');
// 可跳转到登录页
// window.location.href = '/login';
break;
case 403:
ElMessage.error('拒绝访问');
break;
case 404:
ElMessage.error('请求资源不存在');
break;
case 500:
ElMessage.error('服务器内部错误');
break;
default:
ElMessage.error(`请求失败:${response.status}`);
}
} else if (error.request) {
// 请求已发出但无响应(如网络错误、超时)
ElMessage.error('网络异常,请检查网络连接');
} else {
// 其他错误(如配置错误)
ElMessage.error('请求配置错误');
}
return Promise.reject(error);
}
);
export default request;
此时业务层已经做到了数据与状态的解耦,状态问题和错误信息全部在拦截器阶段处理,返回给业务调用接口位置的只有数据:
// 在响应拦截器里面,返回的是 response.data.data,所以业务层里面只会拿到数据
const data = await api.getUser();
这样业务层就不用关心 HTTP 状态码和后端返回的具体结构,也不用对请求错误类型进行具体的区分了。
2、错误分级和策略映射的设计
错误的严重程度是有等级的,不应该把所有的错误都按相同的方式处理,比如 401,一般情况下应该去实现用户无感刷新,请求失败再提示用户重新登陆。
只需要加上一个简单的策略映射设计:
const errorHandlers = {
401() {
logout();
router.push('/login');
},
403() {
ElMessage.error('没有权限');
},
default(err) {
ElMessage.error(err.message || '请求失败');
},
};
并在拦截器中统一调度:
function handleBusinessError(err) {
const handler = errorHandlers[err.code] || errorHandlers.default;
handler(err);
}
这样就能根据状态码的不同,映射不同的处理方法,长期维护和拓展问题也解决了。
3、错误对象标准化
很多项目后期痛苦的根源是 error 有时候是 string,有时候是 AxiosError,有时候是后端对象,所以需要永久地将错误处理的复杂度从业务层转移到基础设置层(即 Axios)。以此来实现一个架构层面的收益。
比如定义一个标准错误结构(这部分代码仅 ts 需要):
interface AppError {
type: 'network' | 'business';
code?: number;
message: string;
raw?: any;
}
然后在 Axios 层构造,举个例子:
axios.interceptors.response.use(
(res) => {
if (res.data.code !== 0) {
return Promise.reject({
type: 'business',
code: res.data.code,
message: res.data.msg,
raw: res,
});
}
return res.data.data;
},
(error) => {
return Promise.reject({
type: 'network',
message: '网络异常,请稍后重试',
raw: error,
});
}
);
那么在业务层就只需要面对一种 error 类型了,不用操心错误类型,业务代码降维:
try {
await api.save();
ElMessage.success('保存成功');
} catch (err) {
ElMessage.error(err.message);
}
三、结语
一个成熟的 Axios 错误处理体系,应该能做到错误集中处理,业务代码干净,错误分级,有明确策略,错误结构统一,方便扩展,自然接入登录、权限、数据监控等模块。**错误处理不是异常流程,而是系统设计的一部分。**做好网络层的错误处理,实现数据与状态的解耦,会让业务层开发大有裨益。


