// bad
try {
doSomethingMightThrowError();
} catch (error) {
console.log(error);
}
// good
try {
doSomethingMightThrowError();
} catch (error){
console.error(error);
message.error(error.message);
logger.log(error);
}
Don't ignore rejected promises!
不要轻易忽略Promise的异常,除非你确定它已经被处理了!
这一块我们还是有血泪教训的,在接入AEM的项目中曾经在脚本异常的上报里将disable_unhandled_rejection开启,禁止捕获了所有Promise异常,当时是基于我们线上应用大部分的promise异常都是umi-request请求接口出错和antd表单验证错误,且未带来什么线上问题,于是就天真的认为未捕获的promise异常毫无危害;这个想法同样危险,因为深入跟踪发现接口请求出错请求库捕获了异常并使用了message.error进行处理辅助论坛,表单验证错误的异常同样是antd在处理完之后选择继续向上抛出,这两者确实没什么危害,可当我们面对这些更多未做处理的Promise异常时候(比如接口返回成功但约定的数据格式错误)同时又不做上报,我们就损失了很多线上问题的案发现场,只能抓瞎去盲猜复现,依赖用户反馈。
查看以下案例:
// bad
fetchData().then(doSomethingMightThrowError).catch(console.log);
// good
fetchData()
.then(doSomethingMightThrowError)
.catch(error => {
console.error(error);
message.error(error.message);
logger.log(error);
});
Exceptions Hierarchy
使用自定义异常,让异常层次结构分明。
管理好业务代码中的异常是非常酷的一件事,上面章节有介绍到Javascript给我们提供的一些基础的异常类型,这些异常类型并不与我们的业务相关。所以使用这些异常来控制代码中的错误也显得不那么恰当,我们的代码正是对我们业务的建模。同样的,我们也要将与业务相关的这些异常建模管理,对异常进行语义化,并在业务逻辑发生特定情况时触发。否则就算调用方捕获了异常也不知道该如何去处理。
这样做往往会带来一些好处:
1、使用error instanceof CustomBizError更容易识别异常,会让判断逻辑更简洁且已读,更容易处理捕获到的异常并恢复程序。
2、通过标准化我们的自定义错误类,让我们更容易做上层处理,比如上面有提到的接口异常我可以选择不作为脚本异常全局上报,因为通常在接口异常里就已经上报了该信息;
参考以下例子
export class RequestException extends Error {
constructor(message) {
super(`RequestException: ${mesage}`);
}
}
export class AccountException extends Error {
constructor(message) {
super(`AccountException: ${message}`);
}
}
const AccountController = {
getAccount: (id) => {
...
throw new RequestException('请求账户信息失败!');
...
}
}
// 客户端代码,创建账户
const id = 1;
const account = AccountController.getAccount(id);
if(account){
throw new AccountException('账户已存在!');
}