errorHandling.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import { pauseTracking, resetTracking } from '@vue/reactivity'
  2. import type { VNode } from './vnode'
  3. import type { ComponentInternalInstance } from './component'
  4. import { popWarningContext, pushWarningContext, warn } from './warning'
  5. import { isFunction, isPromise } from '@vue/shared'
  6. import { LifecycleHooks } from './enums'
  7. // contexts where user provided function may be executed, in addition to
  8. // lifecycle hooks.
  9. export enum ErrorCodes {
  10. SETUP_FUNCTION,
  11. RENDER_FUNCTION,
  12. WATCH_GETTER,
  13. WATCH_CALLBACK,
  14. WATCH_CLEANUP,
  15. NATIVE_EVENT_HANDLER,
  16. COMPONENT_EVENT_HANDLER,
  17. VNODE_HOOK,
  18. DIRECTIVE_HOOK,
  19. TRANSITION_HOOK,
  20. APP_ERROR_HANDLER,
  21. APP_WARN_HANDLER,
  22. FUNCTION_REF,
  23. ASYNC_COMPONENT_LOADER,
  24. SCHEDULER,
  25. }
  26. export const ErrorTypeStrings: Record<LifecycleHooks | ErrorCodes, string> = {
  27. [LifecycleHooks.SERVER_PREFETCH]: 'serverPrefetch hook',
  28. [LifecycleHooks.BEFORE_CREATE]: 'beforeCreate hook',
  29. [LifecycleHooks.CREATED]: 'created hook',
  30. [LifecycleHooks.BEFORE_MOUNT]: 'beforeMount hook',
  31. [LifecycleHooks.MOUNTED]: 'mounted hook',
  32. [LifecycleHooks.BEFORE_UPDATE]: 'beforeUpdate hook',
  33. [LifecycleHooks.UPDATED]: 'updated',
  34. [LifecycleHooks.BEFORE_UNMOUNT]: 'beforeUnmount hook',
  35. [LifecycleHooks.UNMOUNTED]: 'unmounted hook',
  36. [LifecycleHooks.ACTIVATED]: 'activated hook',
  37. [LifecycleHooks.DEACTIVATED]: 'deactivated hook',
  38. [LifecycleHooks.ERROR_CAPTURED]: 'errorCaptured hook',
  39. [LifecycleHooks.RENDER_TRACKED]: 'renderTracked hook',
  40. [LifecycleHooks.RENDER_TRIGGERED]: 'renderTriggered hook',
  41. [ErrorCodes.SETUP_FUNCTION]: 'setup function',
  42. [ErrorCodes.RENDER_FUNCTION]: 'render function',
  43. [ErrorCodes.WATCH_GETTER]: 'watcher getter',
  44. [ErrorCodes.WATCH_CALLBACK]: 'watcher callback',
  45. [ErrorCodes.WATCH_CLEANUP]: 'watcher cleanup function',
  46. [ErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
  47. [ErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
  48. [ErrorCodes.VNODE_HOOK]: 'vnode hook',
  49. [ErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
  50. [ErrorCodes.TRANSITION_HOOK]: 'transition hook',
  51. [ErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
  52. [ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
  53. [ErrorCodes.FUNCTION_REF]: 'ref function',
  54. [ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
  55. [ErrorCodes.SCHEDULER]:
  56. 'scheduler flush. This is likely a Vue internals bug. ' +
  57. 'Please open an issue at https://github.com/vuejs/core .',
  58. }
  59. export type ErrorTypes = LifecycleHooks | ErrorCodes
  60. export function callWithErrorHandling(
  61. fn: Function,
  62. instance: ComponentInternalInstance | null,
  63. type: ErrorTypes,
  64. args?: unknown[],
  65. ) {
  66. try {
  67. return args ? fn(...args) : fn()
  68. } catch (err) {
  69. handleError(err, instance, type)
  70. }
  71. }
  72. export function callWithAsyncErrorHandling(
  73. fn: Function | Function[],
  74. instance: ComponentInternalInstance | null,
  75. type: ErrorTypes,
  76. args?: unknown[],
  77. ): any[] {
  78. if (isFunction(fn)) {
  79. const res = callWithErrorHandling(fn, instance, type, args)
  80. if (res && isPromise(res)) {
  81. res.catch(err => {
  82. handleError(err, instance, type)
  83. })
  84. }
  85. return res
  86. }
  87. const values = []
  88. for (let i = 0; i < fn.length; i++) {
  89. values.push(callWithAsyncErrorHandling(fn[i], instance, type, args))
  90. }
  91. return values
  92. }
  93. export function handleError(
  94. err: unknown,
  95. instance: ComponentInternalInstance | null,
  96. type: ErrorTypes,
  97. throwInDev = true,
  98. ) {
  99. const contextVNode = instance ? instance.vnode : null
  100. if (instance) {
  101. let cur = instance.parent
  102. // the exposed instance is the render proxy to keep it consistent with 2.x
  103. const exposedInstance = instance.proxy
  104. // in production the hook receives only the error code
  105. const errorInfo = __DEV__
  106. ? ErrorTypeStrings[type]
  107. : `https://vuejs.org/error-reference/#runtime-${type}`
  108. while (cur) {
  109. const errorCapturedHooks = cur.ec
  110. if (errorCapturedHooks) {
  111. for (let i = 0; i < errorCapturedHooks.length; i++) {
  112. if (
  113. errorCapturedHooks[i](err, exposedInstance, errorInfo) === false
  114. ) {
  115. return
  116. }
  117. }
  118. }
  119. cur = cur.parent
  120. }
  121. // app-level handling
  122. const appErrorHandler = instance.appContext.config.errorHandler
  123. if (appErrorHandler) {
  124. pauseTracking()
  125. callWithErrorHandling(
  126. appErrorHandler,
  127. null,
  128. ErrorCodes.APP_ERROR_HANDLER,
  129. [err, exposedInstance, errorInfo],
  130. )
  131. resetTracking()
  132. return
  133. }
  134. }
  135. logError(err, type, contextVNode, throwInDev)
  136. }
  137. function logError(
  138. err: unknown,
  139. type: ErrorTypes,
  140. contextVNode: VNode | null,
  141. throwInDev = true,
  142. ) {
  143. if (__DEV__) {
  144. const info = ErrorTypeStrings[type]
  145. if (contextVNode) {
  146. pushWarningContext(contextVNode)
  147. }
  148. warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`)
  149. if (contextVNode) {
  150. popWarningContext()
  151. }
  152. // crash in dev by default so it's more noticeable
  153. if (throwInDev) {
  154. throw err
  155. } else if (!__TEST__) {
  156. console.error(err)
  157. }
  158. } else {
  159. // recover in prod to reduce the impact on end-user
  160. console.error(err)
  161. }
  162. }