vue3-yuanma/packages/runtime-core/src/errorHandling.ts

159 lines
4.8 KiB
TypeScript
Raw Normal View History

import { VNode } from './vnode'
2019-09-06 16:58:31 +00:00
import { ComponentInternalInstance, LifecycleHooks } from './component'
import { warn, pushWarningContext, popWarningContext } from './warning'
import { isPromise, isFunction } from '@vue/shared'
// contexts where user provided function may be executed, in addition to
// lifecycle hooks.
2019-09-06 16:58:31 +00:00
export const enum ErrorCodes {
SETUP_FUNCTION,
RENDER_FUNCTION,
WATCH_GETTER,
WATCH_CALLBACK,
WATCH_CLEANUP,
NATIVE_EVENT_HANDLER,
COMPONENT_EVENT_HANDLER,
VNODE_HOOK,
2019-08-31 21:06:39 +00:00
DIRECTIVE_HOOK,
TRANSITION_HOOK,
2019-09-03 22:11:04 +00:00
APP_ERROR_HANDLER,
APP_WARN_HANDLER,
FUNCTION_REF,
ASYNC_COMPONENT_LOADER,
SCHEDULER
}
export const ErrorTypeStrings: Record<number | string, string> = {
[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-06 16:58:31 +00: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',
[ErrorCodes.VNODE_HOOK]: 'vnode hook',
2019-09-06 16:58:31 +00:00
[ErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
[ErrorCodes.TRANSITION_HOOK]: 'transition hook',
2019-09-06 16:58:31 +00:00
[ErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
[ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
[ErrorCodes.FUNCTION_REF]: 'ref function',
[ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
2019-09-06 16:58:31 +00:00
[ErrorCodes.SCHEDULER]:
2019-09-12 05:52:14 +00:00
'scheduler flush. This is likely a Vue internals bug. ' +
'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue-next'
}
2019-09-06 16:58:31 +00:00
export type ErrorTypes = LifecycleHooks | ErrorCodes
export function callWithErrorHandling(
2019-08-30 16:22:41 +00:00
fn: Function,
2019-09-06 16:58:31 +00:00
instance: ComponentInternalInstance | null,
type: ErrorTypes,
2019-10-22 15:26:48 +00:00
args?: unknown[]
2019-08-30 16:22:41 +00:00
) {
2019-10-22 15:26:48 +00:00
let res
2019-08-30 16:22:41 +00:00
try {
res = args ? fn(...args) : fn()
} catch (err) {
handleError(err, instance, type)
}
return res
}
export function callWithAsyncErrorHandling(
fn: Function | Function[],
2019-09-06 16:58:31 +00:00
instance: ComponentInternalInstance | null,
type: ErrorTypes,
2019-10-22 15:26:48 +00:00
args?: unknown[]
): any[] {
if (isFunction(fn)) {
const res = callWithErrorHandling(fn, instance, type, args)
2020-04-30 18:45:11 +00:00
if (res && isPromise(res)) {
res.catch(err => {
handleError(err, instance, type)
})
}
return res
}
const values = []
for (let i = 0; i < fn.length; i++) {
values.push(callWithAsyncErrorHandling(fn[i], instance, type, args))
}
return values
}
export function handleError(
err: unknown,
2019-09-06 16:58:31 +00:00
instance: ComponentInternalInstance | null,
type: ErrorTypes
) {
const contextVNode = instance ? instance.vnode : null
2019-09-03 22:11:04 +00:00
if (instance) {
let cur = instance.parent
2019-09-03 22:11:04 +00:00
// the exposed instance is the render proxy to keep it consistent with 2.x
const exposedInstance = instance.proxy
2019-09-03 22:11:04 +00:00
// in production the hook receives only the error code
const errorInfo = __DEV__ ? ErrorTypeStrings[type] : type
while (cur) {
const errorCapturedHooks = cur.ec
2020-03-18 22:14:51 +00:00
if (errorCapturedHooks) {
2019-09-03 22:11:04 +00:00
for (let i = 0; i < errorCapturedHooks.length; i++) {
if (errorCapturedHooks[i](err, exposedInstance, errorInfo)) {
return
}
}
}
2019-09-03 22:11:04 +00:00
cur = cur.parent
}
// app-level handling
const appErrorHandler = instance.appContext.config.errorHandler
if (appErrorHandler) {
callWithErrorHandling(
appErrorHandler,
null,
2019-09-06 16:58:31 +00:00
ErrorCodes.APP_ERROR_HANDLER,
2019-09-03 22:11:04 +00:00
[err, exposedInstance, errorInfo]
)
return
}
}
logError(err, type, contextVNode)
}
2019-12-01 17:03:26 +00:00
// Test-only toggle for testing the unhandled warning behavior
2019-11-04 16:24:22 +00:00
let forceRecover = false
export function setErrorRecovery(value: boolean) {
forceRecover = value
}
function logError(err: unknown, type: ErrorTypes, contextVNode: VNode | null) {
2019-09-02 16:09:29 +00:00
// default behavior is crash in prod & test, recover in dev.
2019-11-04 16:24:22 +00:00
if (__DEV__ && (forceRecover || !__TEST__)) {
const info = ErrorTypeStrings[type]
if (contextVNode) {
pushWarningContext(contextVNode)
}
warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`)
console.error(err)
if (contextVNode) {
popWarningContext()
}
} else {
throw err
}
}