mirror of https://github.com/vuejs/core.git
fix(runtime-core): errors during component patch should be caught by error handlers
This commit is contained in:
parent
3d34f406ac
commit
ee0248accf
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
type VNode,
|
||||||
createApp,
|
createApp,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
h,
|
h,
|
||||||
|
@ -11,6 +12,7 @@ import {
|
||||||
watch,
|
watch,
|
||||||
watchEffect,
|
watchEffect,
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
|
import { ErrorCodes, ErrorTypeStrings } from '../src/errorHandling'
|
||||||
|
|
||||||
describe('error handling', () => {
|
describe('error handling', () => {
|
||||||
test('propagation', () => {
|
test('propagation', () => {
|
||||||
|
@ -609,5 +611,33 @@ describe('error handling', () => {
|
||||||
expect(handler).toHaveBeenCalledTimes(1)
|
expect(handler).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('errors in scheduler job with owner instance should be caught', async () => {
|
||||||
|
let vnode: VNode
|
||||||
|
const x = ref(0)
|
||||||
|
const app = createApp({
|
||||||
|
render() {
|
||||||
|
return (vnode = vnode || h('div', x.value))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
app.config.errorHandler = vi.fn()
|
||||||
|
app.mount(nodeOps.createElement('div'))
|
||||||
|
|
||||||
|
const error = new Error('error')
|
||||||
|
Object.defineProperty(vnode!, 'el', {
|
||||||
|
get() {
|
||||||
|
throw error
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
x.value++
|
||||||
|
await nextTick()
|
||||||
|
expect(app.config.errorHandler).toHaveBeenCalledWith(
|
||||||
|
error,
|
||||||
|
{},
|
||||||
|
ErrorTypeStrings[ErrorCodes.COMPONENT_UPDATE],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
// native event handler handling should be tested in respective renderers
|
// native event handler handling should be tested in respective renderers
|
||||||
})
|
})
|
||||||
|
|
|
@ -23,6 +23,7 @@ export enum ErrorCodes {
|
||||||
FUNCTION_REF,
|
FUNCTION_REF,
|
||||||
ASYNC_COMPONENT_LOADER,
|
ASYNC_COMPONENT_LOADER,
|
||||||
SCHEDULER,
|
SCHEDULER,
|
||||||
|
COMPONENT_UPDATE,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ErrorTypeStrings: Record<LifecycleHooks | ErrorCodes, string> = {
|
export const ErrorTypeStrings: Record<LifecycleHooks | ErrorCodes, string> = {
|
||||||
|
@ -54,16 +55,15 @@ export const ErrorTypeStrings: Record<LifecycleHooks | ErrorCodes, string> = {
|
||||||
[ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
|
[ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
|
||||||
[ErrorCodes.FUNCTION_REF]: 'ref function',
|
[ErrorCodes.FUNCTION_REF]: 'ref function',
|
||||||
[ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
|
[ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
|
||||||
[ErrorCodes.SCHEDULER]:
|
[ErrorCodes.SCHEDULER]: 'scheduler flush',
|
||||||
'scheduler flush. This is likely a Vue internals bug. ' +
|
[ErrorCodes.COMPONENT_UPDATE]: 'component update',
|
||||||
'Please open an issue at https://github.com/vuejs/core .',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ErrorTypes = LifecycleHooks | ErrorCodes
|
export type ErrorTypes = LifecycleHooks | ErrorCodes
|
||||||
|
|
||||||
export function callWithErrorHandling(
|
export function callWithErrorHandling(
|
||||||
fn: Function,
|
fn: Function,
|
||||||
instance: ComponentInternalInstance | null,
|
instance: ComponentInternalInstance | null | undefined,
|
||||||
type: ErrorTypes,
|
type: ErrorTypes,
|
||||||
args?: unknown[],
|
args?: unknown[],
|
||||||
) {
|
) {
|
||||||
|
@ -105,7 +105,7 @@ export function callWithAsyncErrorHandling(
|
||||||
|
|
||||||
export function handleError(
|
export function handleError(
|
||||||
err: unknown,
|
err: unknown,
|
||||||
instance: ComponentInternalInstance | null,
|
instance: ComponentInternalInstance | null | undefined,
|
||||||
type: ErrorTypes,
|
type: ErrorTypes,
|
||||||
throwInDev = true,
|
throwInDev = true,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1587,6 +1587,7 @@ function baseCreateRenderer(
|
||||||
effect.run()
|
effect.run()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
update.i = instance
|
||||||
update.id = instance.uid
|
update.id = instance.uid
|
||||||
// allowRecurse
|
// allowRecurse
|
||||||
// #1801, #2043 component render effects should allow recursive updates
|
// #1801, #2043 component render effects should allow recursive updates
|
||||||
|
@ -1599,7 +1600,6 @@ function baseCreateRenderer(
|
||||||
effect.onTrigger = instance.rtg
|
effect.onTrigger = instance.rtg
|
||||||
? e => invokeArrayFns(instance.rtg!, e)
|
? e => invokeArrayFns(instance.rtg!, e)
|
||||||
: void 0
|
: void 0
|
||||||
update.ownerInstance = instance
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update()
|
update()
|
||||||
|
|
|
@ -26,9 +26,8 @@ export interface SchedulerJob extends Function {
|
||||||
/**
|
/**
|
||||||
* Attached by renderer.ts when setting up a component's render effect
|
* Attached by renderer.ts when setting up a component's render effect
|
||||||
* Used to obtain component information when reporting max recursive updates.
|
* Used to obtain component information when reporting max recursive updates.
|
||||||
* dev only.
|
|
||||||
*/
|
*/
|
||||||
ownerInstance?: ComponentInternalInstance
|
i?: ComponentInternalInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SchedulerJobs = SchedulerJob | SchedulerJob[]
|
export type SchedulerJobs = SchedulerJob | SchedulerJob[]
|
||||||
|
@ -240,7 +239,11 @@ function flushJobs(seen?: CountMap) {
|
||||||
if (__DEV__ && check(job)) {
|
if (__DEV__ && check(job)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
|
callWithErrorHandling(
|
||||||
|
job,
|
||||||
|
job.i,
|
||||||
|
job.i ? ErrorCodes.COMPONENT_UPDATE : ErrorCodes.SCHEDULER,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -265,7 +268,7 @@ function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob) {
|
||||||
} else {
|
} else {
|
||||||
const count = seen.get(fn)!
|
const count = seen.get(fn)!
|
||||||
if (count > RECURSION_LIMIT) {
|
if (count > RECURSION_LIMIT) {
|
||||||
const instance = fn.ownerInstance
|
const instance = fn.i
|
||||||
const componentName = instance && getComponentName(instance.type)
|
const componentName = instance && getComponentName(instance.type)
|
||||||
handleError(
|
handleError(
|
||||||
`Maximum recursive updates exceeded${
|
`Maximum recursive updates exceeded${
|
||||||
|
|
Loading…
Reference in New Issue