任何一個框架,對於錯誤的處理都是一種必備的能力。在 Vue 中,則是定義了一套對應的錯誤處理規則給到使用者。且在源代碼級別,對部分必要的過程作了必定的錯誤處理。vue
在 Vue 全局設置的 API 中,咱們能夠設置全局錯誤處理函數,用法以下:git
Vue.config.errorHandler = function (err, vm, info) {
// handle error
// `info` 是 Vue 特定的錯誤信息,好比錯誤所在的生命週期鉤子
// 只在 2.2.0+ 可用
}
複製代碼
該函數能夠做爲指定組件的渲染和觀察期間未捕獲錯誤的處理函數。這個處理函數被調用時,可獲取錯誤信息和 Vue 實例。github
若是咱們想要針對本身的應用,對錯誤作統一的收集與處理(如上報後臺系統),那麼該 API 是一個極好的嵌入點。算法
不過值得注意的是,在不一樣 Vue 版本中,該全局 API 做用的範圍會有所不一樣:vuex
從 2.2.0 起,這個鉤子也會捕獲組件生命週期鉤子裏的錯誤。一樣的,當這個鉤子是
undefined
時,被捕獲的錯誤會經過console.error
輸出而避免應用崩潰。api
從 2.4.0 起,這個鉤子也會捕獲 Vue 自定義事件處理函數內部的錯誤了。數組
從 2.6.0 起,這個鉤子也會捕獲
v-on
DOM 監聽器內部拋出的錯誤。另外,若是任何被覆蓋的鉤子或處理函數返回一個 Promise 鏈 (例如 async 函數),則來自其 Promise 鏈的錯誤也會被處理。promise
這是 2.5.0 新增的一個生命鉤子函數。能夠點擊這裏查看詳細。app
它被調用的時機在於:當捕獲一個來自子孫組件的錯誤時被調用。此鉤子會收到三個參數:錯誤對象、發生錯誤的組件實例以及一個包含錯誤來源信息的字符串。此鉤子能夠返回 false
以阻止該錯誤繼續向上傳播。框架
參考官網的解釋:
config.errorHandler
被定義,全部的錯誤仍會發送它,所以這些錯誤仍然會向單一的分析服務的地方進行彙報。errorCaptured
鉤子,則它們將會被相同的錯誤逐個喚起。errorCaptured
鉤子自身拋出了一個錯誤,則這個新錯誤和本來被捕獲的錯誤都會發送給全局的 config.errorHandler
。errorCaptured
鉤子可以返回 false
以阻止錯誤繼續向上傳播。本質上是說「這個錯誤已經被搞定了且應該被忽略」。它會阻止其它任何會被這個錯誤喚起的 errorCaptured
鉤子和全局的 config.errorHandler
。在 Vue 2.6.10 的源碼中,文件src/core/util/error.js
中定義了對於 Vue 內部自身使用的幾個錯誤處理函數。針對同步異常與異步異常,有不一樣處理方式。咱們詳細來看:
處理同步異常的函數是 handleError(err: Error, vm: any, info: string)
。詳細實現:
export function handleError (err: Error, vm: any, info: string) {
// Deactivate deps tracking while processing error handler to avoid possible infinite rendering.
// See: https://github.com/vuejs/vuex/issues/1505
pushTarget()
try {
if (vm) {
let cur = vm
while ((cur = cur.$parent)) {
const hooks = cur.$options.errorCaptured
if (hooks) {
for (let i = 0; i < hooks.length; i++) {
try {
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()
}
}
複製代碼
該代碼對上面提到的「錯誤傳播規則」作了實現。若是一個組件的繼承或父級從屬鏈路中存在多個 errorCaptured
鉤子,則它們將會被相同的錯誤逐個喚起。 errorCaptured
鉤子可以返回 false
以阻止錯誤繼續向上傳播。最後,經過調用 globalHandleError()
方法:
function globalHandleError (err, vm, info) {
if (config.errorHandler) {
try {
return config.errorHandler.call(null, err, vm, info)
} catch (e) {
// if the user intentionally throws the original error in the handler,
// do not log it twice
if (e !== err) {
logError(e, null, 'config.errorHandler')
}
}
}
logError(err, vm, info)
}
複製代碼
globalHandleError()
方法最終調用的是全局的 config.errorHandler()
方法。
對於異步異常怎麼處理呢?也好辦,將異步處理的函數包裹一層,當異步處理函數在執行過程當中出現錯誤的時候,將異常捕獲並處理。具體的實如今invokeWithErrorHandling()
方法:
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
}
複製代碼
代碼中,對包裹函數的返回是不是異步函數作了isPromise()
的判斷:
export function isPromise (val: any): boolean {
return (
isDef(val) &&
typeof val.then === 'function' &&
typeof val.catch === 'function'
)
}
複製代碼
符合異步函數的條件以後,將上文提到的handleError
寫入到 promise.catch 中。
這樣,就完成了對於異步函數的處理過程。
// src/core/instance/lifecycle.js
export function callHook (vm: Component, hook: string) {
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget()
const handlers = vm.$options[hook]
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
popTarget()
}
複製代碼
//src/core/instance/events.js
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
const info = `event handler for "${event}"`
for (let i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info)
}
}
return vm
}
複製代碼
//src/core/vdom/helpers/update-listeners.js
export function createFnInvoker (fns: Function | Array<Function>, vm: ?Component): Function {
function invoker () {
const fns = invoker.fns
if (Array.isArray(fns)) {
const cloned = fns.slice()
for (let i = 0; i < cloned.length; i++) {
invokeWithErrorHandling(cloned[i], null, arguments, vm, `v-on handler`)
}
} else {
// return handler return value for single handlers
return invokeWithErrorHandling(fns, null, arguments, vm, `v-on handler`)
}
}
invoker.fns = fns
return invoker
}
複製代碼
這幾個地方,其實分別對應的就是一開頭所提到的全局 API Vue.config.errorHandler 做用的範圍。
在這篇文章,咱們瞭解了 Vue 的錯誤處理機制。章節內容很少,但願對於讀者瞭解 Vue 內部的原理有一點幫助。
vue源碼解讀文章目錄:
Vue 更多系列: