前端异常处理的设计
2025年5月20日
一、写在前面的几句唠叨
异常处理, 或者说错误处理的重要性
上学时, C语言考试, 结果正确是得分点, 而对于输入边界的控制, 则是满分点.
本质就是要有异常处理的意识.
而到了工作时, 也是一样. 写"正确流程"的代码, 很简单. 但想将各种已知和未知的异常都能处理好, 则非常考验水平.
前端项目, 无论react/vue/angular etc., 都需要对ajax进行异常处理.
本文讨论前端的统一异常处理, 以ajax为切入点, 延伸到其他场景.
本文假定读者有至少3年前端开发经验, 有处理ajax报错的经验
二、先看案例
2.1 让我们将需求一点点展开, 层层推进
假设ajax response的结构是:
successResponse = {
code: 200,
data: {
name: 'zhangsan',
age: 30
}
}
const errorResponse = {
code: 500,
message: '用户名不存在或密码错误'
}
要求:
当code是200时, 正常返回其中的data
当code是500时, 用Message组件显示该message
下面是最普通的处理
缺点: 在每个业务中, 都要处理一遍
{
let userInfo;
getUserInfo().then(res => {
if (res.code === 200) {
userInfo = res.data;} else if (res.code === 500) {
Message.error(res.message);
}
});
}
// 有其他请求, 也需要重复类似的逻辑判断
{
xxx().then(res => {
if (res.code === 200) {
yyy = res.data;
} else if (res.code === 500) {
Message.error(res.message);
}
});
}
以上写法, 也无法应对需求变更, 如:
response结构变更: 缩短变量名 message -> msg
Message改为Notice
增加另一种逻辑, code: 400
那么接下来很容易想到interceptor.
axios等各种http库, 都支持interceptor.
// 增加 response interceptor
api.interceptors.response.use(
(response) => {
const { code, data, message } = response.data
// 简化数据结构 (根据后端返回格式调整)
if (code === 200) {
return data; // 直接返回业务数据
} else if (code === 500) {
Message.error(message); // 弹MessageBox
}
return response;
},
(error) => {
// 错误处理
if (error.response) {
// 服务器返回了错误状态码 (4xx/5xx)
const status = error.response.status;
const message = error.response.data?.message || '请求错误';
switch (status) {
case 401:
alert('未授权');
break;
case 500:
alert('服务器错误');
break;
default:
console.error(`请求错误 ${status}: ${message}`);
}
}
return Promise.reject(error);
}
);
以上设计解决了之前提出的几个问题.
但仍然存在其他问题, 如:
{
const userInfo = await getUserInfo();
// 这里, 不方便判断userInfo是获取成功还是失败了
}
继续改interceptor
const { code, data, message } = response.data
if (code === 200) {
return data;
} else if (code === 500) {
Message.error(message);
return null; // 明确返回null
}
return response;
然后业务层就可以判断了
{
const userInfo = await getUserInfo();
// 这样是可以判断了
if (!userInfo) {
return;
}
showUserInfo(userInfo);
yyy;
zzz;
}
但好像每个请求, 都要做这类判断, 因为每次请求都可以报错.
不过对于以上需求来说, 到也算是实现了, 且不是太难看.
好, 现在继续加需求, 某一个api的调用处, 希望不显示MessageBox, 而是将对应的error message显示在dom上.
简而言之, 即某处调用需要对error单独处理, 不想要通用处理 (场景B)
{
const userInfo = await getUserInfo();
// 场景A, 同上 (用于与场景B对比)
if (!userInfo) {
return;
}
showUserInfo(userInfo);
yyy;
zzz;
}
{
const userInfo = await getUserInfo();
// 场景B, 想要显示在dom上, 不想显示Message
if (!userInfo) {
// 我们会发现, MessageBox的弹出是统一的, 已经无法阻止其弹出了.
showErrorMessage(errMsg); // 且, 无法得到errorMessage
return;
}
}
好, 为了解决上述问题, 可能会想到对api设置options
首先, interceptor需要改一下
const {
headers,
config: { showMsgBox = false },
} = response;
const { code, data, message } = response.data
if (code === 200) {
return data;
} else if (code === 500) {
if (showMsgBox) {
Message.error(message);
return null;
}
return message; // 需要返回message
}
return response;
再修改下调用处
{
const userInfo = await getUserInfo({showMsgBox: false});
let errMsg;
// 需要判断result的类型
if (typeof userInfo === 'string') {
errMsg = userInfo;
}
// 场景B, 想要显示在dom上, 不想显示Message
if (errMsg) {
showErrorMessageOnDom(errMsg);
return;
}
}
以上算是实现了需求, 但很强行, 存在以下问题:
需要新增showMsgBox, 及相应的逻辑处理代码
Api response的结构根据业务不同, 返回的数据结构也不同(interceptor中, 已经有obj, null, string三种形式了)
业务层还是需要判断result类型
未来, 随着showMsgBox此类业务的增加, interceptor需要返回更多种类型, 业务层也需要做更多判断
为了不出现类型不好判断的情况, 可能需要对数据进行包装, 如下:
const {
headers,
config: { showMsgBox = false },
} = response;
const { code, data, message } = response.data
if (code === 200) {
return {type: 'data', data}; // 1
} else if (code === 500) {
if (showMsgBox) {
Message.error(message);
return null;
}
return {type: 'message', data: message}; // 2
}
return {type: 'raw', response}; // 3
以上, 算是解决了, 但无论是增加新业务, 还是修改旧业务, 对心智负担的增加还是很明显.
作者:断空
链接:https://juejin.cn/post/7519779552124207158
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。