errorHandling.ts 4.8 KB

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