源码剖析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错误处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • vue2.0 与 bootstrap datetimepicker的结合使用实例

    vue2.0 与 bootstrap datetimepicker的结合使用实例

    本篇文章主要介绍了vue2.0 与 bootstrap datetimepicker的结合使用实例,非常具有实用价值,需要的朋友可以参考下
    2017-05-05
  • vue实现拍照或录像的示例代码

    vue实现拍照或录像的示例代码

    这篇文章主要为大家详细介绍了如何利用vue实现拍照或录像的功能,文中的示例代码讲解详细,具有一定的参考价值,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-11-11
  • 解决iView Table组件宽度只变大不变小的问题

    解决iView Table组件宽度只变大不变小的问题

    这篇文章主要介绍了解决iView Table组件宽度只变大不变小的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • Ant Design Pro 之 ProTable使用操作

    Ant Design Pro 之 ProTable使用操作

    这篇文章主要介绍了Ant Design Pro 之 ProTable使用操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • 在JS中如何同步修改vue中的变量

    在JS中如何同步修改vue中的变量

    这篇文章主要介绍了在JS中如何同步修改vue中的变量问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • element-vue实现网页锁屏功能(示例代码)

    element-vue实现网页锁屏功能(示例代码)

    这篇文章主要介绍了element-vue实现网页锁屏功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2023-11-11
  • vue利用vue meta info设置每个页面的title与meta信息

    vue利用vue meta info设置每个页面的title与meta信息

    这篇文章主要给大家介绍了关于vue如何利用vue meta info设置每个页面的title与meta信息的相关资料,文中将实现的方法介绍的非常详细,对大家学习或者使用vue具有一定的参考学习价值,需要的朋友可以参考下
    2021-10-10
  • 解决vue2.x中数据渲染以及vuex缓存的问题

    解决vue2.x中数据渲染以及vuex缓存的问题

    本篇文章主要介绍了vue2.x中请求之前数据显示以及vuex缓存的问题,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • 五分钟带你快速了解vue的常用实例方法

    五分钟带你快速了解vue的常用实例方法

    最近项目中又使用了vue,所以来给大家总结下,下面这篇文章主要给大家介绍了关于vue的常用实例方法,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-09-09
  • vue实现手风琴效果

    vue实现手风琴效果

    这篇文章主要为大家详细介绍了vue实现手风琴效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11

最新评论