errorHandling.ts 5.7 KB

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