前言
async/await如何优雅捕获错误在业界一直存在争论,事实上并没有一个完全得劲的法子,就看相对来讲谁更得劲,而且自己用起来最顺手,那么就用哪一种。今天我们分析一下。
哪些阶段可以捕获错误
-
对于Ajax请求,捕获错误分3个阶段,依次是:Ajax拦截器、业务代码捕获、全局捕获。
-
对于来自于第三方库的Promise和非Ajax的Promise,捕获错误分2个阶段:业务代码捕获、全局捕获。
分别解释:
-
Ajax拦截器是第一层捕获,相当于先行捕获,当然仅限于Ajax请求。拦截器可以拦截掉一批错误,当然必须约定好错误特征,然后也要放行一部分错误,交给业务代码捕获。
-
业务代码捕获是本文讨论的重点。
-
全局捕获是最后一层捕获,相当于兜底捕获。用法是
window.addEventListener('unhandledrejection', fn)
。全局捕获主要用于“阻止控制台打印错误”和“统一处理错误”,使用event.preventDefault();
可以阻止报错信息出现在控制台,也就是让控制台干净一点,所谓“统一处理错误”就是某些后续处理如果全局统一,则可以一齐处理,比如可以捕获错误日志,上传到服务器。一个成熟的项目是一定应该有一个全局捕获处理的,这个没必要犟。
总之,先行捕获和兜底捕获就处理了很多错误,也就是说业务代码中就不必再写相关捕获代码了,下文集中讨论在业务代码中如何优雅捕获错误。
方案一:try...catch...
ES官方的解决方案,当然也是大家最看不上的方案。
优点
浏览器直接支持,且代码一目了然。
缺点
-
原本一行 await 让try...catch...搞成了N行,而且2套大括号,相当于2个块级作用域,真的不优雅。
-
接收数据的变量必须在try外部声明,否则没法传递到后续代码。(除非用已经过时的
var
,然而用var
会引起不必要的歧义,所以你根本不应使用var
。)
方案二:await-to-js库
伪代码:
var [error, result] = await to(Promise对象);
if (error) {
alert("xxxxx出错");
return;
}
var [error, result] = await to(Promise对象);
if (error) {
alert("ooooo出错");
return;
}
优点
-
try...catch是2套大括号,现在await-to-js只需要关注error,所以await-to-js是一套if大括号,而且if可以省掉大括号。
-
不存在深层块级作用域,
result
变量可以轻松传递给后续代码。
缺点
-
必须引入一个库,且必须用
to()
包裹Promise对象。 -
将错误和结果包装进一个数组,这种做法有人说觉得很古怪。
方案三:await Promise对象.catch(e=>{...})
这个方案是给Promise对象接一个catch小尾巴。为了完整演示,我加了全局捕获:
window.addEventListener("unhandledrejection", (e) => {
e.preventDefault();
console.log("捕获了", e); // 会执行
});
function p() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(123);
}, 1000);
});
}
const handle = async () => {
const result = await p().catch((err) => {
alert(err);
return Promise.reject(err);
});
console.log(result); // 不会执行
};
handle();
要点是catch函数里必须return
一个reject的Promise,这样才能阻止代码继续执行。当然,也可以throw new Error(err)
,都行。
优点:
-
全原生。
-
不存在变量作用域烦恼。
-
行数比await-to-js还少一行(如果if不用语法糖的话)。
缺点:
-
必须用全局捕获才能阻止错误出现在控制台,但是我说了,全局捕获是项目标配,所以这个缺点不叫缺点。
-
必须继续抛出错误,不过由于await-to-js也至少要写
return false
,都要占行数,所以平手。
不是方案纯属搞笑:用babel插件给await包裹try...catch...
这类插件还不止一个,随便说一个:https://www.npmjs.com/package/babel-plugin-await-add-trycatch
为什么我说这些插件纯属搞笑呢?
你在编译后给await包裹try...catch...有个卵锤子用?你也不能自定义catch分支的代码,所以唯一的卵用就是不让错误暴露在控制台,针对这点我只想说,插件的开发者们就不知道有全局捕获这个玩意么?
总结
不言而喻,高下立判,最优解就是方案三,也就是catch方案,全原生代码,且无缺点。
如果您还有不同观点,请指出。
网友评论