前言
在之前的项目中,需要做全局错误的收集和上报,最后有个头疼的问题就是 Vue watch 中的异步错误无法上报到 errorHandler 里面,然后在某一天我再次阅读 Vue 代码的时候,发现他在 2.6.13 版本上修复了这个问题,开心!!!
例子
大家可以切换 Vue 的版本号,来看看效果,你会发现 <= 2.6.12 版本的 watch 都不会捕获到异步错误
<!-- vue 2.6.12 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
<div id="app">
<button @click='num++'>{{ num }}</button>
</div>
<script>
Vue.config.errorHandler = (err, vm, info) => {
console.log('收集到错误:', err)
}
new Vue({
el: '#app',
data: { num: 100 },
watch: {
async num() {
// 加 await 是为了捕获异步错误
await this.errorFnc()
}
},
methods: {
errorFnc() {
return new Promise((resolve, reject) => {
reject('promise 错误')
})
},
// 或者 async 函数
// async errorFnc() {
// throw 'async 错误'
// },
}
})
</script>
Vue 是如何解决的
2.6.12
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
// Watcher 里面执行回调函数和下面一样,就不贴代码了
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
try {
// 直接执行回调函数
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
return function unwatchFn () {
watcher.teardown()
}
}
2.6.13
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
// Watcher 里面执行回调函数和下面一样,就不贴代码了
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
// 用该函数去执行回调函数
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn () {
watcher.teardown()
}
}
对比版本
我们发现两个版本不同的是执行回调函数的方式变了,通过 invokeWithErrorHandling 执行回调函数,如果是 promise 的话会被 catch,从而被 handleError 报告上去。
export function invokeWithErrorHandling (
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
) {
let res
try {
res = args ? handler.apply(context, args) : handler.call(context)
if (res && !res._isVue && isPromise(res) && !res._handled) {
res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
// issue #9511
// avoid catch triggering multiple times when nested calls
res._handled = true
}
} catch (e) {
handleError(e, vm, info)
}
return res
}
思考
有人可能会问,为什么不 try catch 自己上报错误信息,或者这个有什么用?
-
自己 try catch,重复工作量极大。
-
对 Vue 来说这个是一个很小的修复,但对于一个线上项目来说,如果无法上报你的所有错误,那么有些地方就可能会影响到用户体验,产生用户的流失,甚至让公司财产损失。
Vue 如何进行错误收集和上报
对于我们开发者来说,最好是不用手动上报错误,这会带来很多重复性的工作,我们最好只用关注我们的正常的业务逻辑,而对于 Vue 工程来说,Vue 会自动上报我们的错误,我们只要保证一定的写法,错误就不会丢失。
第一步
我们全局只需要一个上报错误的地方,那就是 Vue 的 errorHandler,Vue 会把所有错误上报到这个函数,你可以直接应用 sentry,或者在这个函数里面调用后台的错误上报接口。
第二步
我们确定了上报错误的地方,下面要做的就是保证所有错误能被 Vue 捕获到,同步任务的错误会被直接捕获,而异步任务的错误,我们必须使用一定的写法。
异步错误
项目中我们最多的是和后台交互,如下
写法一
这个是我在项目中见的最多的写法,一旦使用了 then 来处理异步任务,就意味着我们的错误不会被 Vue 捕获,如果我们 then 回调函数里面出现了错误,我们还得在最后面写一个 .catch 来捕获 then 回调函数里面的错误,这种写法给我们开发者加大了很多的工作量。
mounted() {
// 不会捕获到错误
this.getData()
},
methods: {
getData() {
http.get('xxx').then(data => {
// xxx
}, error => {
// 只能自己上报异步错误
})
}
}
写法二
我们只用换成 async await 来替代我们 then 的写法,所有错误就会被捕获到,而且更加简洁
async mounted() {
// 使用 await 可以捕获到异步错误
await this.getData()
},
methods: {
async getData() {
const data = await http.get('xxx')
// xxx
}
}
如何保证所有人使用 async 语法开发
如果你的项目中大家都可以遵守这种写法,那就不用往下看了。
对于开发项目来说,开发者是不可控的,编码风格也是千变万化,而且就算记住了哪种写法,在实际开发的时候也会有疏忽,还是能用工具解决的就不用口头去约束。
借助 eslint
基于 eslint 我们可以很轻松制定一套规则,但有一些规则是没有的,就需要我们自己开发,我对上面 async 语法的约束写了一个插件:eslint-plugin-leon-rule,大家可以参考下源码或者使用。
如果大家想开发一个属于自己的 eslint 插件,可以参考我这篇文章:教你手写个 eslint 插件
最后
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 爱喝葡萄汁的海豹 原文链接:https://juejin.im/post/7025405415517061127