乐趣区

浅出Vue 错误处理机制errorCaptured、errorHandler

引子
JavaScript 本身是一个弱类型语言,项目中容易发生错误,做好网页错误监控,能帮助开发者迅速定位问题,保证线上稳定。vue 项目需接入公司内部监控平台,本人之前 vue errorHooks 不甚了解, 决定探一探????
介绍 errorHandler、errorCaptured
文档传送门: errorHandler、errorCaptured
errorHandler
指定组件的渲染和观察期间未捕获错误的处理函数。这个处理函数被调用时,可获取错误信息和 Vue 实例
Vue.config.errorHandler = function (err, vm, info) {
#处理错误信息, 进行错误上报
#err 错误对象
#vm Vue 实例
#`info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
#只在 2.2.0+ 可用
}
版本分割点

2.2.0 起,捕获组件生命周期钩子里的错误。同样的,当这个钩子是 undefined 时,被捕获的错误会通过 console.error 输出而避免应用崩溃
2.4.0 起,也会捕获 Vue 自定义事件处理函数内部的错误
2.6.0 起,也会捕获 v-on DOM 监听器内部抛出的错误。另外,如果任何被覆盖的钩子或处理函数返回一个 Promise 链 (例如 async 函数),则来自其 Promise 链的错误也会被处理

errorCaptured
当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播
错误传播规则

默认情况下,如果全局的 config.errorHandler 定义,所有的错误仍会发送它,因此这些错误仍然会向单一的分析服务的地方进行汇报
如果一个组件的继承或父级从属链路中存在多个 errorCaptured 钩子,则它们将会被相同的错误逐个唤起。
如果此 errorCaptured 钩子自身抛出了一个错误,则这个新错误和原本被捕获的错误都会发送给全局的 config.errorHandler,不能捕获异步 promise 内部抛出的错误和自身的错误
一个 errorCaptured 钩子能够返回 false 以阻止错误继续向上传播。本质上是说“这个错误已经被搞定了且应该被忽略”。它会阻止其它任何会被这个错误唤起的 errorCaptured 钩子和全局的 config.errorHandler

错误信息示例 errorHandler、errorCaptured
光说不练,说了白干,呈上结果供各位看官老爷查看
mounted hook 写入未定义的变量,例如:amounted() {a}
Vue.config.errorHandler err、vm、info

Vue.config.errorHandler 抛出同样的错误 throw err
globalHandleError 函数有 e !== err 判断防止 log 两次错误
Vue.config.errorHandler 抛出新的错误 throw new Error(‘ 你好毒 ’)

errorCaptured (err, vm, info) => ?Boolean 类似于 React 错误处理边界
<error-boundary>
<another-component/>
</error-boundary>
Vue.component(‘ErrorBoundary’, {
data: () => ({ error: null}),
errorCaptured (err, vm, info) {
this.error = `${err.stack}\n\nfound in ${info} of component`
return false
},
render (h) {
if (this.error) {
return h(‘pre’, { style: { color: ‘red’}}, this.error)
}
// ignoring edge cases for the sake of demonstration
return this.$slots.default[0]
}
})
正文
copy 半天官网文档,你是 copy 忍者吗☺,各位看官老爷,请往下面看,注意自己使用时的 Vue 版本,避免 err 抓取不到????
解读 error.js 源码
Vue 源码中,异常处理的逻辑放在 /src/core/util/error.js 中
handleError、globalHandleError、invokeWithErrorHandling、logError
handleError
在需要捕获异常的地方调用。首先获取到报错的组件,之后递归查找当前组件的父组件,依次调用 errorCaptured 方法。在遍历调用完所有 errorCaptured 方法、或 errorCaptured 方法有报错时,调用 globalHandleError 方法
globalHandleError
调用全局的 errorHandler 方法,如果 errorHandler 方法自己又报错了呢?生产环境下会使用 console.error 在控制台中输出
invokeWithErrorHandling
更好的异步错误处理,当时写这篇文章时,git history 显示小右哥,一周之前敲的代码,瞬间透心凉,心飞扬
logError
判断环境,选择不同的抛错方式。非生产环境下,调用 warn 方法处理错误
errorCaptured 和 errorHandler 的触发时机都是相同的,不同的是 errorCaptured 发生在前,且如果某个组件的 errorCaptured 方法返回了 false,那么这个异常信息不会再向上冒泡也不会再调用 errorHandler 方法
/* @flow */
# Vue 全局配置, 也就是上面的 Vue.config
import config from ‘../config’
import {warn} from ‘./debug’
# 判断环境
import {inBrowser, inWeex} from ‘./env’
# 判断是否是 Promise,通过 val.then === ‘function’ && val.catch === ‘function’, val!=== null && val !== undefined
import {isPromise} from ‘shared/util’
# 当错误函数处理错误时,停用 deps 跟踪以避免可能出现的 infinite rendering
# 解决以下出现的问题 https://github.com/vuejs/vuex/issues/1505 的问题
import {pushTarget, popTarget} from ‘../observer/dep’

export function handleError (err: Error, vm: any, info: string) {
// Deactivate deps tracking while processing error handler to avoid possible infinite rendering.
pushTarget()
try {
# vm 指当前报错的组件实例
if (vm) {
let cur = vm
# 首先获取到报错的组件,之后递归查找当前组件的父组件,依次调用 errorCaptured 方法。
# 在遍历调用完所有 errorCaptured 方法、或 errorCaptured 方法有报错时,调用 globalHandleError 方法
while ((cur = cur.$parent)) {
const hooks = cur.$options.errorCaptured
# 判断是否存在 errorCaptured 钩子函数
if (hooks) {
# 选项合并的策略,钩子函数会被保存在一个数组中
for (let i = 0; i < hooks.length; i++) {
# 如果 errorCaptured 钩子执行自身抛出了错误,
# 则用 try{}catch{}捕获错误,将这个新错误和原本被捕获的错误都会发送给全局的 config.errorHandler
# 调用 globalHandleError 方法
try {
# 当前 errorCaptured 执行,根据返回是否是 false 值
# 是 false,capture = true,阻止其它任何会被这个错误唤起的 errorCaptured 钩子和全局的 config.errorHandler
# 是 true capture = fale,组件的继承或父级从属链路中存在的多个 errorCaptured 钩子,会被相同的错误逐个唤起
# 调用对应的钩子函数,处理错误
const capture = hooks[i].call(cur, err, vm, info) === false
if (capture) return
} catch (e) {
globalHandleError(e, cur, ‘errorCaptured hook’)
}
}
}
}
}
# 除非禁止错误向上传播,否则都会调用全局的错误处理函数
globalHandleError(err, vm, info)
} finally {
popTarget()
}
}
# 异步错误处理函数
export function invokeWithErrorHandling (
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
) {
let res
try {
# 根据参数选择不同的 handle 执行方式
res = args ? handler.apply(context, args) : handler.call(context)
# handle 返回结果存在
# res._isVue an flag to avoid this being observed,如果传入值的_isVue 为 ture 时 (即传入的值是 Vue 实例本身) 不会新建 observer 实例
# isPromise(res) 判断 val.then === ‘function’ && val.catch === ‘function’, val!=== null && val !== undefined
# !res._handled _handle 是 Promise 实例的内部变量之一,默认是 false,代表 onFulfilled,onRejected 是否被处理
if (res && !res._isVue && isPromise(res) && !res._handled) {
res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
# avoid catch triggering multiple times when nested calls
# 避免嵌套调用时 catch 多次的触发
res._handled = true
}
} catch (e) {
# 处理执行错误
handleError(e, vm, info)
}
return res
}

# 全局错误处理
function globalHandleError (err, vm, info) {
# 获取全局配置,判断是否设置处理函数,默认 undefined
# 已配置
if (config.errorHandler) {
# try{}catch{} 住全局错误处理函数
try {
# 执行设置的全局错误处理函数,handle error 想干啥就干啥????
return config.errorHandler.call(null, err, vm, info)
} catch (e) {
# 如果开发者在 errorHandler 函数中手动抛出同样错误信息 throw err
# 判断 err 信息是否相等,避免 log 两次
# 如果抛出新的错误信息 throw err Error(‘ 你好毒 ’),将会一起 log 输出
if (e !== err) {
logError(e, null, ‘config.errorHandler’)
}
}
}
# 未配置常规 log 输出
logError(err, vm, info)
}

# 错误输出函数
function logError (err, vm, info) {
if (process.env.NODE_ENV !== ‘production’) {
warn(`Error in ${info}: “${err.toString()}”`, vm)
}
/* istanbul ignore else */
if ((inBrowser || inWeex) && typeof console !== ‘undefined’) {
console.error(err)
} else {
throw err
}
}
欢乐时光
以上是本人对 vue 错误处理的浅显理解,欢迎大家评论交流,共同进步,enjoy !
参考文档:
vue 错误 apivue 错误处理 Promise 源码剖析 vue/issues/7074

退出移动版