源码剖析Vue3中如何进行错误处理

 更新时间:2024年01月29日 10:39:49   作者:云浪  
错误处理是框架设计的核心要素之一,框架的错误处理好坏,直接决定用户应用程序的健壮性以及用户开发应用时处理错误的心智负担,本文将从源码入手,剖析一下Vue3中是如何进行错误处理的,需要的可以参考下

错误处理

错误处理是框架设计的核心要素之一。框架的错误处理好坏,直接决定用户应用程序的健壮性以及用户开发应用时处理错误的心智负担。同时,Vue 作为一个基础地前端框架,也提供了统一地全局错误处理接口,用户可以通过该接口(errorhandler)注册自定义的错误处理程序来全局地处理框架产生的所有异常。

Vue3 中提供了两种处理异常的函数,同步异常处理函数(callWithErrorHandling)和异步异常处理函数(callWithAsyncErrorHandling)。异步异常处理函数是在同步异常处理函数的基础上实现的。

// packages/runtime-core/src/errorHandling.ts

export function callWithErrorHandling(
  fn: Function,
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  args?: unknown[]
) {
  let res
  try {
    res = args ? fn(...args) : fn()
  } catch (err) {
    handleError(err, instance, type)
  }
  return res
}

本篇文章的源码均摘自 Vue.js 3.2.45

从整体上来看,callWithErrorHandling 函数的实现是比较简单的,就是对 try catch 的封装。catch 捕获到的异常统一由 handleError 函数来处理。

// packages/runtime-core/src/errorHandling.ts

export function handleError(
  err: unknown,
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  throwInDev = true
) {
  // 省略其他代码
  logError(err, type, contextVNode, throwInDev)
}
function logError(
  err: unknown,
  type: ErrorTypes,
  contextVNode: VNode | null,
  throwInDev = true
) {
  if (__DEV__) {
    // 省略其他代码
  } else {
    // recover in prod to reduce the impact on end-user
    console.error(err)
  }
}

在 handleError 中会将捕获到的异常输出来。

// packages/runtime-core/src/errorHandling.ts

export function callWithAsyncErrorHandling(
  fn: Function | Function[],
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  args?: unknown[]
): any[] {
  if (isFunction(fn)) {
    const res = callWithErrorHandling(fn, instance, type, args)
    if (res && isPromise(res)) {
      res.catch(err => {
        handleError(err, instance, type)
      })
    }
    return res
  }

  const values = []
  for (let i = 0; i < fn.length; i++) {
    values.push(callWithAsyncErrorHandling(fn[i], instance, type, args))
  }
  return values
}

异步异常处理函数(callWithAsyncErrorHandling),是基于同步异常处理函数(callWithErrorHandling)实现的,如果传入的参数是函数数组(Function[]),则会遍历这个函数数组,递归调用异步异常处理函数(callWithAsyncErrorHandling),将返回的结果保留到数组中(values),最后返回这个数组。

在异步异常处理函数中(callWithAsyncErrorHandling) ,它通过判断传入函数的返回值(res)是否为对象,并且是否有 then 、catch 回调来判断返回值(res)是否为 Promise 实例。这种判断是否为 Promise 实例的方式在我们平时的开发中也可以借鉴一下。

// packages/shared/src/index.ts

export const isFunction = (val: unknown): val is Function =>
  typeof val === 'function'

export const isObject = (val: unknown): val is Record<any, any> =>
  val !== null && typeof val === 'object'

// 判断是否为 Promise 实例
export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
  return isObject(val) && isFunction(val.then) && isFunction(val.catch)
}

Vue3 还提供了 API (errorHandler)用于给用户注册捕获错误的全局处理函数,使用方法如下:

app.config.errorHandler = (err, instance, info) => {
  // 处理错误,例如:报告给一个服务
}

该 API (errorHandler)在 handleError 函数中实现

// packages/runtime-core/src/errorHandling.ts

export function handleError(
  err: unknown,
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  throwInDev = true
) {
  const contextVNode = instance ? instance.vnode : null
  // 省略其他代码
  if (instance) {
    // 读取用户配置的全局错误处理函数
    const appErrorHandler = instance.appContext.config.errorHandler
    if (appErrorHandler) {
      // 如果存在用户配置的全局错误处理函数则放入 callWithErrorHandling 中执行
      callWithErrorHandling(
        appErrorHandler,
        null,
        ErrorCodes.APP_ERROR_HANDLER,
        [err, exposedInstance, errorInfo]
      )
      return
    }
  }
  // 省略其他代码
}

Vue3 会判断用户是否配置了全局错误处理函数,如果配置了则会丢给内置的同步异常处理函数执行(callWithErrorHandling)。由于用户配置的全局错误处理函数执行是给同步异常处理函数执行的,因此,用户在自定义全局错误处理函数时,要注意兼容异步错误的情况,即最好在自定义全局处理函数中,加上对异步错误代码的处理,因为 Vue3 内部没法捕获到异步的错误。

如果要做前端的异常监控,我们完全可以借助 errorHandler 函数,完成前端异常的上报。

Vue3 还提供了在捕获了后代组件传递的错误时调用的生命周期钩子(onErrorCaptured()):

function onErrorCaptured(callback: ErrorCapturedHook): void

type ErrorCapturedHook = (
  err: unknown,
  instance: ComponentPublicInstance | null,
  info: string
) => boolean | void

该生命周期钩子(onErrorCaptured()) 也是在 handleError 函数实现

// packages/runtime-core/src/errorHandling.ts

export function handleError(
  err: unknown,
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  throwInDev = true
) {
  const contextVNode = instance ? instance.vnode : null
  if (instance) {
    // 1. 获取父组件实例
    let cur = instance.parent
    const exposedInstance = instance.proxy
    const errorInfo = __DEV__ ? ErrorTypeStrings[type] : type
    // 2. 开启 while 循环,不断向上遍历,取得父组件实例
    while (cur) {
      // 3. 从父组件实例中获取 onErrorCaptured 生命周期钩子
      const errorCapturedHooks = cur.ec
      if (errorCapturedHooks) {
        // 4. 遍历 onErrorCaptured 生命周期钩子数组
        for (let i = 0; i < errorCapturedHooks.length; i++) {
          if (
            // 5. 执行 onErrorCaptured 生命周期钩子,
            // onErrorCaptured 返回 false 则退出 while 循环,
            // 错误会停止向上传递
            errorCapturedHooks[i](err, exposedInstance, errorInfo) === false
          ) {
            return
          }
        }
      }
      cur = cur.parent
    }
  }
}

由于 Vue3 的组件可以多次注册同一个生命周期钩子,所以 Vue3 内部使用了数组来存储 onErrorCaptured 生命周期钩子。Vue3 内部会使用 while 循环,不断向上遍历取得父组件实例的 onErrorCaptured 生命周期钩子,然后遍历执行 onErrorCaptured 生命周期钩子,如果 onErrorCaptured 生命周期钩子返回 false ,则会退出 while 循环,停止向上传递错误。

错误码与错误信息

前端在与后端进行接口联调的时候,肯定见过各种 HTTP 的状态码,比如 404(无法找到资源)、500(服务器内部错误)等。状态码的好处是可以让用户快速地知道当前 HTTP 请求的状态,方便用户排查错误等。

因此,在 Vue 中,也定义了各种错误码(状态码),用来区分各种类型的错误,方便开发者(Vue 用户)排查错误。

// packages/runtime-core/src/errorHandling.ts

export const enum ErrorCodes {
  SETUP_FUNCTION,
  RENDER_FUNCTION,
  WATCH_GETTER,
  WATCH_CALLBACK,
  WATCH_CLEANUP,
  NATIVE_EVENT_HANDLER,
  COMPONENT_EVENT_HANDLER,
  VNODE_HOOK,
  DIRECTIVE_HOOK,
  TRANSITION_HOOK,
  APP_ERROR_HANDLER,
  APP_WARN_HANDLER,
  FUNCTION_REF,
  ASYNC_COMPONENT_LOADER,
  SCHEDULER
}

// 不同的状态码对应不同的错误信息
export const ErrorTypeStrings: Record<number | string, string> = {
  // 省略其他代码
  [ErrorCodes.SETUP_FUNCTION]: 'setup function',
  [ErrorCodes.RENDER_FUNCTION]: 'render function',
  [ErrorCodes.WATCH_GETTER]: 'watcher getter',
  [ErrorCodes.WATCH_CALLBACK]: 'watcher callback',
  [ErrorCodes.WATCH_CLEANUP]: 'watcher cleanup function',
  [ErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
  [ErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
  [ErrorCodes.VNODE_HOOK]: 'vnode hook',
  [ErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
  [ErrorCodes.TRANSITION_HOOK]: 'transition hook',
  [ErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
  [ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
  [ErrorCodes.FUNCTION_REF]: 'ref function',
  [ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
  [ErrorCodes.SCHEDULER]:
    'scheduler flush. This is likely a Vue internals bug. ' +
    'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/core'
}

Vue 会根据不同的错误码取不同的错误信息输出,方便用户排查错误。

// packages/runtime-core/src/errorHandling.ts

// 省略了一些无关的代码
function logError(
  err: unknown,
  type: ErrorTypes,
  contextVNode: VNode | null,
  throwInDev = true
) {
  if (__DEV__) {
    // 根据错误码取出错误信息输出
    const info = ErrorTypeStrings[type]
    warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`)
  } else {
    console.error(err)
  }
}

同时为了控制生产环境框架的代码体积,利用了 Tree Shaking 机制,仅在开发环境(__DEV__)输出内部的错误信息。

总结

错误处理是框架设计的核心要素之一,Vue3 内部提供了两种处理异常的函数,同步异常处理函数(callWithErrorHanding)和异步异常处理函数(callWithErrorHanding),异步异常处理函数、给用户自定义的全局错误处理函数、捕获了子组件异常后调用的生命周期钩子(onErrorCaptured)均是基于同步异常处理函数(callWithErrorHanding)实现,而同步异常处理函数(callWithErrorHanding)本质是对 try catch 的封装。

Vue3 还提供了错误码和对应的错误信息来帮助开发者(Vue 的用户)快速地排查错误。

从错误处理到错误码和错误信息,可以看出 Vue 的错误处理做得是比较完善的。

到此这篇关于源码剖析Vue3中如何进行错误处理的文章就介绍到这了,更多相关Vue3错误处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • vue中路由跳转不计入history的操作

    vue中路由跳转不计入history的操作

    这篇文章主要介绍了vue中路由跳转不计入history的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • vue.js diff算法原理详细解析

    vue.js diff算法原理详细解析

    这篇文章主要介绍了vue.js diff算法原理详细解析,diff算法可以看作是一种对比算法,对比的对象是新旧虚拟Dom。顾名思义,diff算法可以找到新旧虚拟Dom之间的差异,但diff算法中其实并不是只有对比虚拟Dom,还有根据对比后的结果更新真实Dom
    2022-06-06
  • Vue中JSON文件神奇应用fetch、axios异步加载与模块导入全指南详细教程

    Vue中JSON文件神奇应用fetch、axios异步加载与模块导入全指南详细教程

    在Vue中使用JSON文件有多种方式,包括使用fetch方法加载JSON文件、使用axios库加载JSON文件,以及将JSON文件导入为模块,这篇文章主要介绍了Vue中JSON文件神奇应用fetch、axios异步加载与模块导入全指南详细教程,需要的朋友可以参考下
    2024-01-01
  • VueJS全面解析

    VueJS全面解析

    Vue.js(读音 /vju&#720;/, 类似于 view) 是一套构建用户界面的渐进式框架。与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计。这篇文章主要介绍了VueJS全面解析的相关资料,需要的朋友可以参考下
    2016-11-11
  • 利用vue.js把静态json绑定bootstrap的table方法

    利用vue.js把静态json绑定bootstrap的table方法

    今天小编就为大家分享一篇利用vue.js把静态json绑定bootstrap的table方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-08-08
  • 完美解决iview 的select下拉框选项错位的问题

    完美解决iview 的select下拉框选项错位的问题

    下面小编就为大家分享一篇完美解决iview 的select下拉框选项错位的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-03-03
  • Vue数据驱动模拟实现2

    Vue数据驱动模拟实现2

    这篇文章主要介绍了Vue数据驱动模拟实现的相关资料,实现Observer构造函数,监听已有数据data中的所有属性,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-01-01
  • Vue提示框组件vue-notification使用详解

    Vue提示框组件vue-notification使用详解

    这篇文章主要介绍了Vue提示框组件vue-notification使用详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-03-03
  • 详解如何使用 vue-cli 开发多页应用

    详解如何使用 vue-cli 开发多页应用

    本篇文章主要介绍了详解如何使用 vue-cli 开发多页应用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-12-12
  • 查看vue-cli脚手架的版本号和vue真实版本号及详细操作命令

    查看vue-cli脚手架的版本号和vue真实版本号及详细操作命令

    本文给大家分享如何查看vue-cli脚手架的版本号和vue真实版本号及详细操作过程,本文给大家讲解的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2022-11-11

最新评论