Vue 的错误处理机制

任何一个框架,对于错误的处理都是一种必备的能力。在 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

生命周期钩子 errorCaptured

这是 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 中。

这样,就完成了对于异步函数的处理过程。

源码在何处使用了异步异常捕获

hook钩子函数
// 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
  }
复制代码
v-on 监听器
//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构造函数与初始化过程

(二):数据响应式与实现

(三):数组的响应式处理

(四):Vue的异步更新队列

(五):虚拟DOM的引入

(六):数据更新算法--patch算法

(七):组件化机制的实现

(八):计算属性与侦听属性

(九):编译过程的 optimize 阶段

Vue 更多系列:

Vue的错误处理机制

以手写代码的方式解析 Vue 的工做过程

Vue Router的手写实现

相关文章
相关标签/搜索