errorHandling.ts 4.6 KB

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