怎么处理vue项目中的错误 - 图1

一、错误类型

任何一个框架,对于错误的处理都是一种必备的能力

Vue 中,则是定义了一套对应的错误处理规则给到使用者,且在源代码级别,对部分必要的过程做了一定的错误处理。

主要的错误来源包括:

  • 后端接口错误
  • 代码中本身逻辑错误

二、如何处理

后端接口错误

通过axiosinterceptor实现网络请求的response先进行一层拦截

  1. apiClient.interceptors.response.use(
  2. response => {
  3. return response;
  4. },
  5. error => {
  6. if (error.response.status == 401) {
  7. router.push({ name: "Login" });
  8. } else {
  9. message.error("出错了");
  10. return Promise.reject(error);
  11. }
  12. }
  13. );

代码逻辑问题

全局设置错误处理

设置全局错误处理函数

  1. Vue.config.errorHandler = function (err, vm, info) {
  2. // handle error
  3. // `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
  4. // 只在 2.2.0+ 可用
  5. }

errorHandler指定组件的渲染和观察期间未捕获错误的处理函数。这个处理函数被调用时,可获取错误信息和 Vue 实例

不过值得注意的是,在不同Vue 版本中,该全局 API 作用的范围会有所不同:

从 2.2.0 起,这个钩子也会捕获组件生命周期钩子里的错误。同样的,当这个钩子是 undefined 时,被捕获的错误会通过 console.error 输出而避免应用崩

从 2.4.0 起,这个钩子也会捕获 Vue 自定义事件处理函数内部的错误了

从 2.6.0 起,这个钩子也会捕获 v-on DOM 监听器内部抛出的错误。另外,如果任何被覆盖的钩子或处理函数返回一个 Promise 链 (例如 async 函数),则来自其 Promise 链的错误也会被处理

生命周期钩子

errorCaptured是 2.5.0 新增的一个生命钩子函数,当捕获到一个来自子孙组件的错误时被调用

基本类型

  1. (err: Error, vm: Component, info: string) => ?boolean

此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播

参考官网,错误传播规则如下:

  • 默认情况下,如果全局的 config.errorHandler 被定义,所有的错误仍会发送它,因此这些错误仍然会向单一的分析服务的地方进行汇报
  • 如果一个组件的继承或父级从属链路中存在多个 errorCaptured 钩子,则它们将会被相同的错误逐个唤起。
  • 如果此 errorCaptured 钩子自身抛出了一个错误,则这个新错误和原本被捕获的错误都会发送给全局的 config.errorHandler
  • 一个 errorCaptured 钩子能够返回 false 以阻止错误继续向上传播。本质上是说“这个错误已经被搞定了且应该被忽略”。它会阻止其它任何会被这个错误唤起的 errorCaptured 钩子和全局的 config.errorHandler

下面来看个例子

定义一个父组件cat

  1. Vue.component('cat', {
  2. template:`
  3. <div>
  4. <h1>Cat: </h1>
  5. <slot></slot>
  6. </div>`,
  7. props:{
  8. name:{
  9. required:true,
  10. type:String
  11. }
  12. },
  13. errorCaptured(err,vm,info) {
  14. console.log(`cat EC: ${err.toString()}\ninfo: ${info}`);
  15. return false;
  16. }
  17. });

定义一个子组件kitten,其中dontexist()并没有定义,存在错误

  1. Vue.component('kitten', {
  2. template:'<div><h1>Kitten: {{ dontexist() }}</h1></div>',
  3. props:{
  4. name:{
  5. required:true,
  6. type:String
  7. }
  8. }
  9. });

页面中使用组件

  1. <div id="app" v-cloak>
  2. <cat name="my cat">
  3. <kitten></kitten>
  4. </cat>
  5. </div>

在父组件的errorCaptured则能够捕获到信息

  1. cat EC: TypeError: dontexist is not a function
  2. info: render

三、源码分析

异常处理源码

源码位置:/src/core/util/error.js

  1. // Vue 全局配置,也就是上面的Vue.config
  2. import config from '../config'
  3. import { warn } from './debug'
  4. // 判断环境
  5. import { inBrowser, inWeex } from './env'
  6. // 判断是否是Promise,通过val.then === 'function' && val.catch === 'function', val !=== null && val !== undefined
  7. import { isPromise } from 'shared/util'
  8. // 当错误函数处理错误时,停用deps跟踪以避免可能出现的infinite rendering
  9. // 解决以下出现的问题https://github.com/vuejs/vuex/issues/1505的问题
  10. import { pushTarget, popTarget } from '../observer/dep'
  11. export function handleError (err: Error, vm: any, info: string) {
  12. // Deactivate deps tracking while processing error handler to avoid possible infinite rendering.
  13. pushTarget()
  14. try {
  15. // vm指当前报错的组件实例
  16. if (vm) {
  17. let cur = vm
  18. // 首先获取到报错的组件,之后递归查找当前组件的父组件,依次调用errorCaptured 方法。
  19. // 在遍历调用完所有 errorCaptured 方法、或 errorCaptured 方法有报错时,调用 globalHandleError 方法
  20. while ((cur = cur.$parent)) {
  21. const hooks = cur.$options.errorCaptured
  22. // 判断是否存在errorCaptured钩子函数
  23. if (hooks) {
  24. // 选项合并的策略,钩子函数会被保存在一个数组中
  25. for (let i = 0; i < hooks.length; i++) {
  26. // 如果errorCaptured 钩子执行自身抛出了错误,
  27. // 则用try{}catch{}捕获错误,将这个新错误和原本被捕获的错误都会发送给全局的config.errorHandler
  28. // 调用globalHandleError方法
  29. try {
  30. // 当前errorCaptured执行,根据返回是否是false值
  31. // 是false,capture = true,阻止其它任何会被这个错误唤起的 errorCaptured 钩子和全局的 config.errorHandler
  32. // 是true capture = fale,组件的继承或父级从属链路中存在的多个 errorCaptured 钩子,会被相同的错误逐个唤起
  33. // 调用对应的钩子函数,处理错误
  34. const capture = hooks[i].call(cur, err, vm, info) === false
  35. if (capture) return
  36. } catch (e) {
  37. globalHandleError(e, cur, 'errorCaptured hook')
  38. }
  39. }
  40. }
  41. }
  42. }
  43. // 除非禁止错误向上传播,否则都会调用全局的错误处理函数
  44. globalHandleError(err, vm, info)
  45. } finally {
  46. popTarget()
  47. }
  48. }
  49. // 异步错误处理函数
  50. export function invokeWithErrorHandling (
  51. handler: Function,
  52. context: any,
  53. args: null | any[],
  54. vm: any,
  55. info: string
  56. ) {
  57. let res
  58. try {
  59. // 根据参数选择不同的handle执行方式
  60. res = args ? handler.apply(context, args) : handler.call(context)
  61. // handle返回结果存在
  62. // res._isVue an flag to avoid this being observed,如果传入值的_isVue为ture时(即传入的值是Vue实例本身)不会新建observer实例
  63. // isPromise(res) 判断val.then === 'function' && val.catch === 'function', val !=== null && val !== undefined
  64. // !res._handled _handle是Promise 实例的内部变量之一,默认是false,代表onFulfilled,onRejected是否被处理
  65. if (res && !res._isVue && isPromise(res) && !res._handled) {
  66. res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
  67. // avoid catch triggering multiple times when nested calls
  68. // 避免嵌套调用时catch多次的触发
  69. res._handled = true
  70. }
  71. } catch (e) {
  72. // 处理执行错误
  73. handleError(e, vm, info)
  74. }
  75. return res
  76. }
  77. //全局错误处理
  78. function globalHandleError (err, vm, info) {
  79. // 获取全局配置,判断是否设置处理函数,默认undefined
  80. // 已配置
  81. if (config.errorHandler) {
  82. // try{}catch{} 住全局错误处理函数
  83. try {
  84. // 执行设置的全局错误处理函数,handle error 想干啥就干啥💗
  85. return config.errorHandler.call(null, err, vm, info)
  86. } catch (e) {
  87. // 如果开发者在errorHandler函数中手动抛出同样错误信息throw err
  88. // 判断err信息是否相等,避免log两次
  89. // 如果抛出新的错误信息throw err Error('你好毒'),将会一起log输出
  90. if (e !== err) {
  91. logError(e, null, 'config.errorHandler')
  92. }
  93. }
  94. }
  95. // 未配置常规log输出
  96. logError(err, vm, info)
  97. }
  98. // 错误输出函数
  99. function logError (err, vm, info) {
  100. if (process.env.NODE_ENV !== 'production') {
  101. warn(`Error in ${info}: "${err.toString()}"`, vm)
  102. }
  103. /* istanbul ignore else */
  104. if ((inBrowser || inWeex) && typeof console !== 'undefined') {
  105. console.error(err)
  106. } else {
  107. throw err
  108. }
  109. }

小结

  • handleError在需要捕获异常的地方调用,首先获取到报错的组件,之后递归查找当前组件的父组件,依次调用errorCaptured 方法,在遍历调用完所有 errorCaptured 方法或 errorCaptured 方法有报错时,调用 globalHandleError 方法
  • globalHandleError调用全局的 errorHandler 方法,再通过logError判断环境输出错误信息
  • invokeWithErrorHandling更好的处理异步错误信息
  • logError判断环境,选择不同的抛错方式。非生产环境下,调用warn方法处理错误