前端错误采集

965 阅读4分钟

前言

前端可以说是最贴近用户的一层,当产品不断的迭代完善,产品的用户体验会更加趋向于完美,然而前端异常却是很另人头疼的一个问题,我们应该怎么去对待这些异常呢?

异常入手考虑方向

函数异常处理的两个层面:

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结合,捕获错误后逻辑统一处理;

总结

最后希望实现一个健全的架构捕获所有同步、异步的异常。业务方不处理异常时,中断函数执行并启用默认处理,业务方也可以随时捕获异常自己处理。 优雅的异常处理方式就像冒泡事件,任何元素可以自由拦截,也可以放任不管交给顶层处理。