Routine的个人博客

前端异常处理的设计

一、写在前面的几句唠叨

异常处理, 或者说错误处理的重要性

上学时, 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

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。