前言
前端可以说是最贴近用户的一层,当产品不断的迭代完善,产品的用户体验会更加趋向于完美,然而前端异常却是很另人头疼的一个问题,我们应该怎么去对待这些异常呢?
异常入手考虑方向
函数异常处理的两个层面:
1.防患于未然,从一开始就不要让异常发生。
2.异常还是出现了,该怎么去处理出现的异常。
主动防御
1.入参:在 ES6 的到来后,函数的入参写法已经得到了质的提高和优化。
function print(obj = {}) {
console.log('name', obj.name)
console.log('age', obj.age)
}
2.函数内表达式语句
function print(obj = {}) {
console.log('name:', obj.name || '未知姓名')
console.log('age:', obj.age || '未知年龄')
}
如果这样的话,那你会发现表达式语句就变得比较鲁棒性了,但是还不够好,这样写不够抽象,我们换种方式稍微把表达式语句给解耦一下,代码如下:
function print(obj = {}) {
const { name = '未知姓名', age = '未知年龄' } = obj
console.log('name:', name)
console.log('age:', age)
}
这样的话,看起来就感觉好多了,其实还可以再抽象,比如吧 console.log 封装成 log 函数,通过调用 log(name) ,就能完成 console.log('name:', name) 的功能。
3.对于我们操作的数据,尤其是由 API 接口返回的,时常会有一个很复杂的深层嵌套的数据结构。为了代码的健壮性,很多时候需要对每一层访问都作空值判断。比如这样的数据:
props:{
user:{
posts[
{
comments: '评论内容'
}
]
}
}
我们经常会这样写提高鲁棒性:
let coments = props && props.user && props.user.posts && props.user.posts[0].comments
实际上我们可以这样封装下:
function getIn(p, o) {
return p.reduce(function(xs, x) {
return (xs && xs[x]) ? xs[x] : null;
}, o);
}
然后我们就可以像这样调用:
let comments = getIn(['user', 'posts', 0, 'comments'], props);
如果正常访问到,则返回对应的值,否则返回 null。
异常出现
对于前端来说,我们可做的异常捕获还真不少,大概如下:
1. JS 语法错误、代码异常
2. AJAX 请求异常
3. 静态资源加载异常
4. Promise 异常
5. Iframe 异常
6. 跨域 Script error
7. 崩溃和卡顿
已知的错误都在开发时处理掉了。需要捕获的是未知的错误,包括:
1.未知的接口返回内容
2.未知的资源加载情况,如图片或其他媒体资源。
3.改了一处代码,没发现另一处被影响到的逻辑
4.较深层级的交互逻辑,没有在测试阶段被发现的问题
能够想到的处理手段:
try-catch
try-catch 只能捕获到同步的运行时错误,对语法和异步错误却无能为力,捕获不到。
原因:回调函数有同步和异步之分,区别在于对方执行回调函数的时机;异步回调中,回调函数的执行栈与原函数分离开,导致外部无法抓住异常。
window.onerror
当 JS 运行时错误发生时,window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror()。限制:语法错误,静态资源异常,无法捕获到。
小结:在实际的使用过程中,onerror 主要是来捕获预料之外的错误,而 try-catch 则是用来在可预见情况下监控特定的错误,两者结合使用更加高效。
window.addEventListener
当一项资源(如图片或脚本)加载失败,加载资源的元素会触发一个 Event 接口的 error 事件,并执行该元素上的onerror() 处理函数。这些 error 事件不会向上冒泡到 window ,不过(至少在 Firefox 中)能被单一的window.addEventListener 捕获。
对于Async/Await 捕获异常
可以使用 try catch 捕获异常,因为此时的异步其实在一个作用域中,通过 generator 控制执行顺序,所以可以将异步看做同步的代码去编写,包括使用 try catch 捕获异常。
Vue 中异常
如果使用vue等MVVM框架,vue源码中在关键钩子执行前包了一层try/catch,所以此时一般不会被onerro全局捕获;
// vue源码片段
function callHook (vm, hook) {
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget();
var handlers = vm.$options[hook];
if (handlers) {
for (var i = 0, j = handlers.length; i < j; i++) {
try {
handlers[i].call(vm);
} catch (e) {
handleError(e, vm, (hook + " hook"));
}
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook);
}
popTarget();
}
Vue 中异常处理包含以下几个方面的技巧:
errorHandler
warnHandler
renderError
errorCaptured
对于errorHandler,如果开发者没有配置Vue.config.errorHandler,那么捕获到的错误会以console.error的方式输出。
改写console.error
考虑到vue项目错误,我们可以对console.error做下改造,让每一次触发console.error的时候我们可以做一些事情,例如对错误收集系统做一下上报什么的。如下:
console.error = (func => {
return (...args) => {
// 在这里就可以收集到console.error的错误
// 做一些事情
func.apply(console, args);
}
})(console.error);
改造后,可以和try catch结合,捕获错误后逻辑统一处理;
总结
最后希望实现一个健全的架构捕获所有同步、异步的异常。业务方不处理异常时,中断函数执行并启用默认处理,业务方也可以随时捕获异常自己处理。 优雅的异常处理方式就像冒泡事件,任何元素可以自由拦截,也可以放任不管交给顶层处理。