2024-03-19 20:47:52 +08:00
|
|
|
import { pauseTracking, resetTracking } from '@vue/reactivity'
|
2019-08-31 00:16:09 +08:00
|
|
|
import type { VNode } from './vnode'
|
2023-01-26 15:25:55 +08:00
|
|
|
import type { ComponentInternalInstance } from './component'
|
2019-08-31 00:16:09 +08:00
|
|
|
import { popWarningContext, pushWarningContext, warn } from './warning'
|
2024-07-17 10:05:09 +08:00
|
|
|
import { EMPTY_OBJ, isArray, isFunction, isPromise } from '@vue/shared'
|
2023-01-26 15:25:55 +08:00
|
|
|
import { LifecycleHooks } from './enums'
|
2019-08-31 00:16:09 +08:00
|
|
|
|
|
|
|
// contexts where user provided function may be executed, in addition to
|
|
|
|
// lifecycle hooks.
|
2023-11-29 12:24:50 +08:00
|
|
|
export enum ErrorCodes {
|
2019-09-17 01:15:20 +08:00
|
|
|
SETUP_FUNCTION,
|
2019-08-31 03:05:39 +08:00
|
|
|
RENDER_FUNCTION,
|
|
|
|
WATCH_GETTER,
|
2019-08-31 00:16:09 +08:00
|
|
|
WATCH_CALLBACK,
|
2019-08-31 03:05:39 +08:00
|
|
|
WATCH_CLEANUP,
|
2019-08-31 00:16:09 +08:00
|
|
|
NATIVE_EVENT_HANDLER,
|
|
|
|
COMPONENT_EVENT_HANDLER,
|
2020-03-18 23:43:32 +08:00
|
|
|
VNODE_HOOK,
|
2019-09-01 05:06:39 +08:00
|
|
|
DIRECTIVE_HOOK,
|
2019-11-21 10:56:17 +08:00
|
|
|
TRANSITION_HOOK,
|
2019-09-04 06:11:04 +08:00
|
|
|
APP_ERROR_HANDLER,
|
|
|
|
APP_WARN_HANDLER,
|
2019-10-29 00:03:30 +08:00
|
|
|
FUNCTION_REF,
|
2020-03-22 04:01:08 +08:00
|
|
|
ASYNC_COMPONENT_LOADER,
|
2019-08-31 00:16:09 +08:00
|
|
|
SCHEDULER,
|
2024-07-12 01:24:17 +08:00
|
|
|
COMPONENT_UPDATE,
|
2024-04-29 18:47:56 +08:00
|
|
|
APP_UNMOUNT_CLEANUP,
|
2019-08-31 00:16:09 +08:00
|
|
|
}
|
|
|
|
|
2023-01-26 15:25:55 +08:00
|
|
|
export const ErrorTypeStrings: Record<LifecycleHooks | ErrorCodes, string> = {
|
2021-06-29 03:30:20 +08:00
|
|
|
[LifecycleHooks.SERVER_PREFETCH]: 'serverPrefetch hook',
|
2019-08-31 00:16:09 +08:00
|
|
|
[LifecycleHooks.BEFORE_CREATE]: 'beforeCreate hook',
|
|
|
|
[LifecycleHooks.CREATED]: 'created hook',
|
|
|
|
[LifecycleHooks.BEFORE_MOUNT]: 'beforeMount hook',
|
|
|
|
[LifecycleHooks.MOUNTED]: 'mounted hook',
|
|
|
|
[LifecycleHooks.BEFORE_UPDATE]: 'beforeUpdate hook',
|
|
|
|
[LifecycleHooks.UPDATED]: 'updated',
|
|
|
|
[LifecycleHooks.BEFORE_UNMOUNT]: 'beforeUnmount hook',
|
|
|
|
[LifecycleHooks.UNMOUNTED]: 'unmounted hook',
|
|
|
|
[LifecycleHooks.ACTIVATED]: 'activated hook',
|
|
|
|
[LifecycleHooks.DEACTIVATED]: 'deactivated hook',
|
|
|
|
[LifecycleHooks.ERROR_CAPTURED]: 'errorCaptured hook',
|
|
|
|
[LifecycleHooks.RENDER_TRACKED]: 'renderTracked hook',
|
|
|
|
[LifecycleHooks.RENDER_TRIGGERED]: 'renderTriggered hook',
|
2019-09-07 00:58:31 +08:00
|
|
|
[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',
|
2020-03-18 23:43:32 +08:00
|
|
|
[ErrorCodes.VNODE_HOOK]: 'vnode hook',
|
2019-09-07 00:58:31 +08:00
|
|
|
[ErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
|
2019-11-21 10:56:17 +08:00
|
|
|
[ErrorCodes.TRANSITION_HOOK]: 'transition hook',
|
2019-09-07 00:58:31 +08:00
|
|
|
[ErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
|
|
|
|
[ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
|
2019-10-29 00:03:30 +08:00
|
|
|
[ErrorCodes.FUNCTION_REF]: 'ref function',
|
2020-03-22 04:01:08 +08:00
|
|
|
[ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
|
2024-07-12 01:24:17 +08:00
|
|
|
[ErrorCodes.SCHEDULER]: 'scheduler flush',
|
|
|
|
[ErrorCodes.COMPONENT_UPDATE]: 'component update',
|
2024-04-29 18:47:56 +08:00
|
|
|
[ErrorCodes.APP_UNMOUNT_CLEANUP]: 'app unmount cleanup function',
|
2019-08-31 00:16:09 +08:00
|
|
|
}
|
|
|
|
|
2019-09-07 00:58:31 +08:00
|
|
|
export type ErrorTypes = LifecycleHooks | ErrorCodes
|
2019-08-31 00:16:09 +08:00
|
|
|
|
2019-08-31 03:05:39 +08:00
|
|
|
export function callWithErrorHandling(
|
2019-08-31 00:22:41 +08:00
|
|
|
fn: Function,
|
2024-07-12 01:24:17 +08:00
|
|
|
instance: ComponentInternalInstance | null | undefined,
|
2019-09-07 00:58:31 +08:00
|
|
|
type: ErrorTypes,
|
2019-10-22 23:26:48 +08:00
|
|
|
args?: unknown[],
|
2019-08-31 00:22:41 +08:00
|
|
|
) {
|
|
|
|
try {
|
2024-02-13 17:15:18 +08:00
|
|
|
return args ? fn(...args) : fn()
|
2019-08-31 00:22:41 +08:00
|
|
|
} catch (err) {
|
|
|
|
handleError(err, instance, type)
|
|
|
|
}
|
2019-08-31 00:16:09 +08:00
|
|
|
}
|
|
|
|
|
2019-08-31 03:05:39 +08:00
|
|
|
export function callWithAsyncErrorHandling(
|
2019-10-22 01:59:10 +08:00
|
|
|
fn: Function | Function[],
|
2019-09-07 00:58:31 +08:00
|
|
|
instance: ComponentInternalInstance | null,
|
|
|
|
type: ErrorTypes,
|
2019-10-22 23:26:48 +08:00
|
|
|
args?: unknown[],
|
2024-04-15 22:37:16 +08:00
|
|
|
): any {
|
2019-10-22 01:59:10 +08:00
|
|
|
if (isFunction(fn)) {
|
|
|
|
const res = callWithErrorHandling(fn, instance, type, args)
|
2020-05-01 02:45:11 +08:00
|
|
|
if (res && isPromise(res)) {
|
2020-03-10 03:58:52 +08:00
|
|
|
res.catch(err => {
|
2019-10-22 01:59:10 +08:00
|
|
|
handleError(err, instance, type)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2024-04-15 22:37:16 +08:00
|
|
|
if (isArray(fn)) {
|
|
|
|
const values = []
|
|
|
|
for (let i = 0; i < fn.length; i++) {
|
|
|
|
values.push(callWithAsyncErrorHandling(fn[i], instance, type, args))
|
|
|
|
}
|
|
|
|
return values
|
|
|
|
} else if (__DEV__) {
|
|
|
|
warn(
|
|
|
|
`Invalid value type passed to callWithAsyncErrorHandling(): ${typeof fn}`,
|
|
|
|
)
|
2019-08-31 03:05:39 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-31 00:16:09 +08:00
|
|
|
export function handleError(
|
2020-03-10 03:58:52 +08:00
|
|
|
err: unknown,
|
2024-07-12 01:24:17 +08:00
|
|
|
instance: ComponentInternalInstance | null | undefined,
|
2020-09-16 23:10:16 +08:00
|
|
|
type: ErrorTypes,
|
|
|
|
throwInDev = true,
|
2019-08-31 00:16:09 +08:00
|
|
|
) {
|
|
|
|
const contextVNode = instance ? instance.vnode : null
|
2024-07-17 10:05:09 +08:00
|
|
|
const { errorHandler, throwUnhandledErrorInProduction } =
|
|
|
|
(instance && instance.appContext.config) || EMPTY_OBJ
|
2019-09-04 06:11:04 +08:00
|
|
|
if (instance) {
|
2019-10-05 22:38:02 +08:00
|
|
|
let cur = instance.parent
|
2019-09-04 06:11:04 +08:00
|
|
|
// the exposed instance is the render proxy to keep it consistent with 2.x
|
2019-12-11 00:14:29 +08:00
|
|
|
const exposedInstance = instance.proxy
|
2019-09-04 06:11:04 +08:00
|
|
|
// in production the hook receives only the error code
|
2023-11-28 07:38:36 +08:00
|
|
|
const errorInfo = __DEV__
|
|
|
|
? ErrorTypeStrings[type]
|
2024-01-18 20:00:01 +08:00
|
|
|
: `https://vuejs.org/error-reference/#runtime-${type}`
|
2019-09-04 06:11:04 +08:00
|
|
|
while (cur) {
|
|
|
|
const errorCapturedHooks = cur.ec
|
2020-03-19 06:14:51 +08:00
|
|
|
if (errorCapturedHooks) {
|
2019-09-04 06:11:04 +08:00
|
|
|
for (let i = 0; i < errorCapturedHooks.length; i++) {
|
2020-10-06 05:37:26 +08:00
|
|
|
if (
|
|
|
|
errorCapturedHooks[i](err, exposedInstance, errorInfo) === false
|
|
|
|
) {
|
2019-09-04 06:11:04 +08:00
|
|
|
return
|
|
|
|
}
|
2019-08-31 00:16:09 +08:00
|
|
|
}
|
|
|
|
}
|
2019-09-04 06:11:04 +08:00
|
|
|
cur = cur.parent
|
|
|
|
}
|
|
|
|
// app-level handling
|
2024-07-17 10:05:09 +08:00
|
|
|
if (errorHandler) {
|
2024-03-19 20:47:52 +08:00
|
|
|
pauseTracking()
|
2024-07-17 10:05:09 +08:00
|
|
|
callWithErrorHandling(errorHandler, null, ErrorCodes.APP_ERROR_HANDLER, [
|
|
|
|
err,
|
|
|
|
exposedInstance,
|
|
|
|
errorInfo,
|
|
|
|
])
|
2024-03-19 20:47:52 +08:00
|
|
|
resetTracking()
|
2019-09-04 06:11:04 +08:00
|
|
|
return
|
2019-08-31 00:16:09 +08:00
|
|
|
}
|
|
|
|
}
|
2024-07-17 10:05:09 +08:00
|
|
|
logError(err, type, contextVNode, throwInDev, throwUnhandledErrorInProduction)
|
2019-08-31 00:16:09 +08:00
|
|
|
}
|
|
|
|
|
2020-09-16 23:10:16 +08:00
|
|
|
function logError(
|
|
|
|
err: unknown,
|
|
|
|
type: ErrorTypes,
|
|
|
|
contextVNode: VNode | null,
|
|
|
|
throwInDev = true,
|
2024-07-17 10:05:09 +08:00
|
|
|
throwInProd = false,
|
2020-09-16 23:10:16 +08:00
|
|
|
) {
|
2020-07-28 12:17:59 +08:00
|
|
|
if (__DEV__) {
|
2019-08-31 00:16:09 +08:00
|
|
|
const info = ErrorTypeStrings[type]
|
|
|
|
if (contextVNode) {
|
|
|
|
pushWarningContext(contextVNode)
|
|
|
|
}
|
|
|
|
warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`)
|
|
|
|
if (contextVNode) {
|
|
|
|
popWarningContext()
|
|
|
|
}
|
2020-09-16 23:10:16 +08:00
|
|
|
// crash in dev by default so it's more noticeable
|
|
|
|
if (throwInDev) {
|
|
|
|
throw err
|
2020-09-17 01:37:12 +08:00
|
|
|
} else if (!__TEST__) {
|
2020-09-16 23:10:16 +08:00
|
|
|
console.error(err)
|
|
|
|
}
|
2024-07-17 10:05:09 +08:00
|
|
|
} else if (throwInProd) {
|
|
|
|
throw err
|
2020-07-28 12:17:59 +08:00
|
|
|
} else {
|
|
|
|
// recover in prod to reduce the impact on end-user
|
|
|
|
console.error(err)
|
2019-08-31 00:16:09 +08:00
|
|
|
}
|
|
|
|
}
|