淺出Vue 錯誤處理機制errorCaptured、errorHandler

引子

JavaScript自己是一個弱類型語言,項目中容易發生錯誤,作好網頁錯誤監控,能幫助開發者迅速定位問題,保證線上穩定。
vue項目需接入公司內部監控平臺,本人以前vue errorHooks不甚瞭解, 決定探一探🖼html

介紹 errorHandler、errorCaptured

文檔傳送門: errorHandlererrorCapturedvue

errorHandler

指定組件的渲染和觀察期間未捕獲錯誤的處理函數。這個處理函數被調用時,可獲取錯誤信息和 Vue 實例git

Vue.config.errorHandler = function (err, vm, info) {
  #處理錯誤信息, 進行錯誤上報
  #err錯誤對象
  #vm Vue實例
  #`info` 是 Vue 特定的錯誤信息,好比錯誤所在的生命週期鉤子
  #只在 2.2.0+ 可用
}
複製代碼

版本分割點github

  • 2.2.0 起,捕獲組件生命週期鉤子裏的錯誤。一樣的,當這個鉤子是 undefined 時,被捕獲的錯誤會經過 console.error 輸出而避免應用崩潰
  • 2.4.0 起,也會捕獲 Vue 自定義事件處理函數內部的錯誤
  • 2.6.0 起,也會捕獲 v-on DOM 監聽器內部拋出的錯誤。另外,若是任何被覆蓋的鉤子或處理函數返回一個 Promise 鏈 (例如 async 函數),則來自其 Promise 鏈的錯誤也會被處理

errorCaptured

當捕獲一個來自子孫組件的錯誤時被調用。此鉤子會收到三個參數:錯誤對象、發生錯誤的組件實例以及一個包含錯誤來源信息的字符串。此鉤子能夠返回 false 以阻止該錯誤繼續向上傳播
vuex

錯誤傳播規則api

  • 默認狀況下,若是全局的 config.errorHandler定義,全部的錯誤仍會發送它,所以這些錯誤仍然會向單一的分析服務的地方進行彙報
  • 若是一個組件的繼承或父級從屬鏈路中存在多個 errorCaptured 鉤子,則它們將會被相同的錯誤逐個喚起。
  • 若是此 errorCaptured 鉤子自身拋出了一個錯誤,則這個新錯誤和本來被捕獲的錯誤都會發送給全局的 config.errorHandler,不能捕獲異步promise內部拋出的錯誤和自身的錯誤
  • 一個 errorCaptured 鉤子可以返回 false 以阻止錯誤繼續向上傳播。本質上是說「這個錯誤已經被搞定了且應該被忽略」。它會阻止其它任何會被這個錯誤喚起的 errorCaptured 鉤子和全局的 config.errorHandler

錯誤信息示例 errorHandler、errorCaptured

光說不練,說了白乾,呈上結果供各位看官老爺查看
數組

mounted hook 寫入未定義的變量,例如:a
mounted() { a }promise

  • 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抓取不到🖤bash

解讀error.js源碼

Vue 源碼中,異常處理的邏輯放在 /src/core/util/error.js 中app

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錯誤api
vue錯誤處理
Promise源碼剖析
vue/issues/7074

相關文章
相關標籤/搜索