又是繁忙的一周。突然发现以前没聊过错误/异常处理,准备分享一下。
这里我就不细究错误(Error)/异常(Exception)的定义了,大体上程序执行过程中,一旦出现就会阻碍后续代码顺利执行的问题,我们都当它们是错误。我们要保证自己的代码能得到顺利执行,就要防止被它们破坏,就要捕获并处理错误。这就是接下来要讨论的要点。
有些同学在这个领域搞得不伦不类:要么从来不捕获任何错误;要么所有地方都丢一个 try ... catch ...
;要么最外层包一个大 try ... catch ...
。这些做法都不对。本文分享我的观点和做法。
开发时能够不出错,尽量不要出错
虽然我自己也整天写 bug,并且被自己的 bug 折麽得死去活来,但我还是要说:我们应该在开发阶段尽量避免出现错误,保证我们的代码能够长时间运行不出错。这就需要我们考虑到各种边界情况,提前做出预判,操作前多做检查,避免出错。
比如,如果使用 node.js 要操作文件,就先判断目标是否存在,目录是否已经创建过。而不是捕获各种可能出现的错误。用户要上传数据给服务器后端处理,就要先尽可能排除掉不可接受的情况,而不是被服务器拒绝后再转达给用户。
捕获无法在开发阶段排除的错误
也有一些错误,我们实在无法在开发阶段排除。最常见的就是网络问题。因为我们无法预期用户在怎样的网络条件下使用我们的产品,而现在用户的使用场景又极为丰富:无论是高速运动的火车,还是跨国网络环境,又或是第三方服务突然挂掉,都可能导致网络不畅,而请求失败。
所以我们必须在发起网络请求时做好错误捕获,并且处理好网络造成的问题。比如:
- 告知用户当前状态,正确渲染 UI
- 保护好未能保存到服务器上的数据
- 提醒用户重试
- 提供其它临时保存方案
简而言之,发起网络请求时没有 try ... catch ...
我在 code review 时是不会通过的。
开发中见到错误要及时处理
我见过一些同学的开发环境,跑起来满屏错误,但是好像也能运行,于是他们就不管。这样做是不行的。因为会被抛出的错误大多是未捕获的(Uncaught
),它们大概率会破坏到某些代码的执行,没有被影响多半只是因为运气好,没有破坏到当前正在执行的逻辑。早晚还是要遭。
另外,如果项目遇到了一些奇怪的问题,不知道该怎么解决;同时控制台里有一些奇奇怪怪的报错,那么解决这些报错很可能会带来意想不到的收获。
总之,不要漏掉错误,不要容忍错误。否则积累技术债。
要理解错误冒泡的中断机制,尤其是异步操作
异步回调中的错误处理比较难做,因为回调前后是两个函数栈,所以执行时函数无法捕获到回调时的错误。举个例子吧:
someAsyncMethod() // 可以直接被外层 try catch 捕获
.then(() => {
// 这里如果发生错误,无法被外层捕获
})
.catch(e => {
// 所以我们通常要在最后处理错误
})
异步函数由于浏览器的升级,可以在堆栈最底层捕获到上层发生的错误。所以我们可以在底层函数,通常来说也就是执行时的函数捕获错。比如,我们有一个业务函数,要调用一个封装好的请求函数,那么,错误处理就应该放在业务函数里,而不是请求函数里:
// 业务代码
async function doDeletePost(id) {
try {
await deleteItem('post', id);
} catch (e) {
// 应该在这里处理错误
}
}
// 这里虽然是异步函数,请求远程接口,但不需要处理错误
async function deleteItem(type, id) {
await fetch(`/api/${type}/${id}, {
method: 'DELETE',
});
}
另外注意,没有 await
的异步函数就不存在错误捕获,不要写出这样的代码:
function doDeletePost(id) {
try {
deleteItem('post', id); // 没有 await,没有意义
} catch (e) {
// 错误处理
}
}
不要封装错误处理,在业务端处理,并提供准确的信息
有些同学喜欢封装错误处理,我觉得当年的 axios 二次封装难辞其咎。对此我坚决反对,比如:后台同步数据出错,可能不需要告知用户;但是用户主动发起的操作失败就必须告知用户。加载数据失败和删除数据失败,其错误信息也是不一样的,应该准确传达给用户。
所以,我们应该在业务端发起请求的地方捕获并处理错误,而不是封装一个通用请求类,并且集中处理可能的错误,给出无法区分的信息。
错误信息要有价值
有些同学拿到错误之后,直接 alert('出错了。')
这种错误信息对用户、对开发者都没有帮助。以网络请求为例,比较常见的做法是:
- 返回的信息首先应该遵守 HTTP 规范,即不同的结果使用不同的响应码。
- 返回的响应体包括
code
字段,用来传输错误码,它跟 HTTP 状态码没关系。成功的结果,code=0
;错误的结果,code
则使用全局唯一的错误码,方便前后端协同排查。 - 错误码可以使用方便理解的规范,比如
XXXX-XXX-X
的形式,从大类到小类再到具体的函数位置等。这样,在任何位置捕获到错误,都可以快速定位到可能出错的环节,进行排查,解决问题。
总结
希望大家都掌握好错误处理的方法,能够妥善、快速的解决问题。如果各位对错误处理有一些特殊的理解,或者对错误处理有疑问,欢迎留言讨论。
欢迎吐槽,共同进步