前言
在initEvents中發現的有意思的東西,就是 Vue 針對 Error 的處理,說實話之前壓根沒在意過 Vue 是如何收集處理 Error 的;
errorHandler:https://v2.cn.vuejs.org/v2/api#errorHandler
?> 從 2.2.0 起,這個鉤子也會捕獲組件生命周期鉤子里的錯誤,同樣的,當這個鉤子是 undefined 時,被捕獲的錯誤會通過 console.error 輸出而避免應用崩潰,
?> 從 2.4.0 起,這個鉤子也會捕獲 Vue 自定義事件處理函式內部的錯誤了,
?> 從 2.6.0 起,這個鉤子也會捕獲 v-on DOM 監聽器內部拋出的錯誤,另外,如果任何被覆寫的鉤子或處理函式回傳一個 Promise 鏈 (例如 async 函式),則來自其 Promise 鏈的錯誤也會被處理,
內容
error.ts位于src/core/util/error.ts
?> 整體的函式呼叫流程如下圖,當然這只是一般情況下的流程,也存在handleError為入口的情況;
具體的內部細節邏輯就直接來看代碼吧,

invokeWithErrorHandling
export function invokeWithErrorHandling(
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
) {
let res
// 通過try catch進行錯誤的捕獲,如果捕獲到錯誤就呼叫handleError函式
try {
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call
// call() 方法接受的是一個引數串列,而 apply() 方法接受的是一個包含多個引數的陣列
// 如果存在args則通過apply進行處理或者通過call進行處理
res = args ? handler.apply(context, args) : handler.call(context)
// 如果存在res & res不是vue實體 & res是個promise函式 & res._handled不為true
if (res && !res._isVue && isPromise(res) && !(res as any)._handled) {
res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
// issue #9511
// avoid catch triggering multiple times when nested calls
// 將_handled設定為true避免在嵌套函式中多次觸發catch
;(res as any)._handled = true
}
} catch (e: any) {
handleError(e, vm, info)
}
return res
}
handleError
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
// 處理錯誤的時候停止deps跟蹤防止無限渲染
pushTarget()
try {
if (vm) {
let cur = vm
// 回圈查找$parent直到查找到vue根實體上,因為根實體上不存在$parent也就是undefined
while ((cur = cur.$parent)) {
// 獲取鉤子errorCaptured
const hooks = cur.$options.errorCaptured
if (hooks) {
for (let i = 0; i < hooks.length; i++) {
try {
// 如果errorCaptured回傳的為false直接return
const capture = hooks[i].call(cur, err, vm, info) === false
if (capture) return
} catch (e: any) {
// https://v2.cn.vuejs.org/v2/api/#errorCaptured
// 執行errorCaptured發生錯誤時呼叫globalHandleError
// 在捕獲一個來自后代組件的錯誤時被呼叫,
// 此鉤子會收到三個引數:錯誤物件、發生錯誤的組件實體以及一個包含錯誤來源資訊的字串,
globalHandleError(e, cur, 'errorCaptured hook')
}
}
}
}
}
// 全域的捕獲
globalHandleError(err, vm, info)
} finally {
popTarget()
}
}
globalHandleError
function globalHandleError(err, vm, info) {
// https://v2.cn.vuejs.org/v2/api/#errorHandler
// 如果全域配置存在.errorHandler則呼叫errorHandler輸出錯誤資訊
// 沒配置的話在瀏覽器環境下會通過console.error列印錯誤
if (config.errorHandler) {
try {
return config.errorHandler.call(null, err, vm, info)
} catch (e: any) {
// if the user intentionally throws the original error in the handler,
// do not log it twice
// 如果通過errorHandler處理發生了錯誤,就直接拋出防止錯誤被二次列印
if (e !== err) {
logError(e, null, 'config.errorHandler')
}
}
}
logError(err, vm, info)
}
logError
// 輸出錯誤資訊
function logError(err, vm, info) {
// DEV環境下直接發出警告
if (__DEV__) {
warn(`Error in ${info}: "${err.toString()}"`, vm)
}
// 如果是瀏覽器環境下且console不為undefined
// 就直接console.error輸出錯誤資訊
// 否則就拋出
/* istanbul ignore else */
if (inBrowser && typeof console !== 'undefined') {
console.error(err)
} else {
throw err
}
}
完整原始碼
import config from '../config'
import { warn } from './debug'
import { inBrowser } from './env'
import { isPromise } from 'shared/util'
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.
// See: https://github.com/vuejs/vuex/issues/1505
// 處理錯誤的時候停止deps跟蹤防止無限渲染
pushTarget()
try {
if (vm) {
let cur = vm
// 回圈查找$parent直到查找到vue根實體上,因為根實體上不存在$parent也就是undefined
while ((cur = cur.$parent)) {
// 獲取鉤子errorCaptured
const hooks = cur.$options.errorCaptured
if (hooks) {
for (let i = 0; i < hooks.length; i++) {
try {
// 如果errorCaptured回傳的為false直接return
const capture = hooks[i].call(cur, err, vm, info) === false
if (capture) return
} catch (e: any) {
// https://v2.cn.vuejs.org/v2/api/#errorCaptured
// 執行errorCaptured發生錯誤時呼叫globalHandleError
// 在捕獲一個來自后代組件的錯誤時被呼叫,
// 此鉤子會收到三個引數:錯誤物件、發生錯誤的組件實體以及一個包含錯誤來源資訊的字串,
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 catch進行錯誤的捕獲,如果捕獲到錯誤就呼叫handleError函式
try {
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call
// call() 方法接受的是一個引數串列,而 apply() 方法接受的是一個包含多個引數的陣列
// 如果存在args則通過apply進行處理或者通過call進行處理
res = args ? handler.apply(context, args) : handler.call(context)
// 如果存在res & res不是vue實體 & res是個promise函式 & res._handled不為true
if (res && !res._isVue && isPromise(res) && !(res as any)._handled) {
res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
// issue #9511
// avoid catch triggering multiple times when nested calls
// 將_handled設定為true避免在嵌套函式中多次觸發catch
;(res as any)._handled = true
}
} catch (e: any) {
handleError(e, vm, info)
}
return res
}
function globalHandleError(err, vm, info) {
// https://v2.cn.vuejs.org/v2/api/#errorHandler
// 如果全域配置存在.errorHandler則呼叫errorHandler輸出錯誤資訊
// 沒配置的話在瀏覽器環境下會通過console.error列印錯誤
if (config.errorHandler) {
try {
return config.errorHandler.call(null, err, vm, info)
} catch (e: any) {
// if the user intentionally throws the original error in the handler,
// do not log it twice
// 如果通過errorHandler處理發生了錯誤,就直接拋出防止錯誤被二次列印
if (e !== err) {
logError(e, null, 'config.errorHandler')
}
}
}
logError(err, vm, info)
}
// 輸出錯誤資訊
function logError(err, vm, info) {
// DEV環境下直接發出警告
if (__DEV__) {
warn(`Error in ${info}: "${err.toString()}"`, vm)
}
// 如果是瀏覽器環境下且console不為undefined
// 就直接console.error輸出錯誤資訊
// 否則就拋出
/* istanbul ignore else */
if (inBrowser && typeof console !== 'undefined') {
console.error(err)
} else {
throw err
}
}
學無止境,謙卑而行.
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/547542.html
標籤:其他
上一篇:Ajax及其應用
