淺出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 寫入未定義的變數,例如:a
mounted() { 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 想幹啥就幹啥:heartpulse: 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 !