如何優雅的處理vue項目異常


如何優雅的處理vue項目異常


背景

  • 你還在為處理Uncaught (in promise) ReferenceError煩惱嗎?
  • 你還在為捕獲異常反覆的寫try catch嗎?
  • 你還在為每一個promise寫catch嗎?

是時候一站式統一處理異常!!!(針對vue項目,包括vuex)

全局異常捕獲

<code>Vue.config.errorHandler = function (err, vm, info) {
// 指定組件的渲染和觀察期間未捕獲錯誤的處理函數。這個處理函數被調用時,可獲取錯誤信息和 Vue 實例。

// handle error
// `info` 是 Vue 特定的錯誤信息,比如錯誤所在的生命週期鉤子
// 只在 2.2.0+ 可用
}
複製代碼/<code>

注意:面對異常處理,同步異常和異步異常應該區別對待分別處理。

vue核心源碼剖析

通過閱讀源碼看一下vue是如何將Vue.config.errorHandler接口暴露給使用者。

同步異常處理方案

<code>// 定義異常處理函數,判斷用戶是否自定義Vue.config.errorHandler,定義則直接調用,未定義執行vue本身異常處理。
function globalHandleError(err, vm, info) {
if (Vue.config.errorHandler) {
try {
return config.errorHandler.call(null, err, vm, info)
} catch (e) {
logError(e, null, 'config.errorHandler');
}
}
logError(err, vm, info);
}
try {
// vue正常執行代碼被包裹在try內,有異常會調用globalHandleError
} catch (e) {
globalHandleError(e, vm, '對應信息');
}
複製代碼/<code>

異步異常處理方案

<code>// 定義異步異常處理函數,對於自身沒有捕獲異常的promise統一執行catch
function invokeWithErrorHandling(
handler,
context,
args,
vm,
info
) {
var res;
try {
res = args ? handler.apply(context, args) : handler.call(context);
if (res && !res._isVue && isPromise(res) && !res._handled) {
res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); });
// 異步代碼例如promise可以統一為其定義Promise.prototype.catch()方法。
res._handled = true;

}
} catch (e) {
handleError(e, vm, info);
}
return res
}

// 所有的鉤子函數調用異常處理函數
function callHook(vm, hook) {
var handlers = vm.$options[hook];
// 為所有鉤子增加異常處理
var info = hook + " hook";
if (handlers) {
for (var i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info);
}
}
}
複製代碼/<code>

知識延伸

<code>// vue接口是能處理同步異常以及部分鉤子中的異步異常,對於方法中的異常無法有效處理,我們可以仿照源碼增加方式中的異步異常處理,避免為每一個promise寫catch
Vue.mixin({
beforeCreate() {
const methods = this.$options.methods || {}
Object.keys(methods).forEach(key => {
let fn = methods[key]
this.$options.methods[key] = function (...args) {
let ret = fn.apply(this, args)
if (ret && typeof ret.then === 'function' && typeof ret.catch === "function") {
return ret.catch(Vue.config.errorHandler)
} else { // 默認錯誤處理
return ret
}
}
})
}
})

// 針對vuex中的action也做了異常統一處理,核心代碼見下面
function registerActionHandle(actions) {
Object.keys(actions).forEach(key => {
let fn = actions[key]
actions[key] = function (...args) {
let ret = fn.apply(this, args)
if (isPromise(ret)) {
return ret.catch(errorHandler)
} else { // 默認錯誤處理
return ret
}
}
})
}
const registerVuex = (instance) => {
if (instance.$options['store']) {
let actions = instance.$options['store']['_actions'] || {}
if (actions) {
let tempActions = {}
Object.keys(actions).forEach(key => {
tempActions[key] = actions[key][0]
})
registerActionHandle(tempActions)
}
}
}

複製代碼/<code>

完整代碼

下面是全局處理異常的完整代碼,已經封裝成一個插件

<code>errorPlugin.js

/**
* 全局異常處理
* @param {
* } error
* @param {*} vm
*/

function isPromise(ret) {
return (ret && typeof ret.then === 'function' && typeof ret.catch === "function")

}
const errorHandler = (error, vm, info) => {
console.error('拋出全局異常')
console.error(vm)
console.error(error)
console.error(info)
}
function registerActionHandle(actions) {
Object.keys(actions).forEach(key => {
let fn = actions[key]
actions[key] = function (...args) {
let ret = fn.apply(this, args)
if (isPromise(ret)) {
return ret.catch(errorHandler)
} else { // 默認錯誤處理
return ret
}
}
})
}
const registerVuex = (instance) => {
if (instance.$options['store']) {
let actions = instance.$options['store']['_actions'] || {}
if (actions) {
let tempActions = {}
Object.keys(actions).forEach(key => {
tempActions[key] = actions[key][0]
})
registerActionHandle(tempActions)
}
}
}
const registerVue = (instance) => {
if (instance.$options.methods) {
let actions = instance.$options.methods || {}
if (actions) {
registerActionHandle(actions)
}
}
}

let GlobalError = {
install: (Vue, options) => {
/**
* 全局異常處理
* @param {
* } error
* @param {*} vm
*/

Vue.config.errorHandler = errorHandler
Vue.mixin({
beforeCreate() {
registerVue(this)
registerVuex(this)
}
})
Vue.prototype.$throw = errorHandler
}
}

export default GlobalError

複製代碼/<code>

使用

<code>// 在入口文件中引入
import ErrorPlugin from './errorPlugin'
import Vue from 'vue'
Vue.use(ErrorPlugin)
複製代碼/<code>

寫在最後

增加全局異常處理有助於

  • 提高代碼健壯性
  • 減少崩潰
  • 快速定位bug

完整代碼

  • https://github.com/s249359986/vue-errorcatch


分享到:


相關文章: