fix(runtime-core/async-component): fix error component when there are no error handlers

fix #2129
This commit is contained in:
Evan You 2020-09-16 11:10:16 -04:00
parent bad0ecb910
commit c7b4a379cf
3 changed files with 67 additions and 7 deletions

View File

@ -206,6 +206,51 @@ describe('api: defineAsyncComponent', () => {
expect(serializeInner(root)).toBe('resolved') expect(serializeInner(root)).toBe('resolved')
}) })
// #2129
test('error with error component, without global handler', async () => {
let resolve: (comp: Component) => void
let reject: (e: Error) => void
const Foo = defineAsyncComponent({
loader: () =>
new Promise((_resolve, _reject) => {
resolve = _resolve as any
reject = _reject
}),
errorComponent: (props: { error: Error }) => props.error.message
})
const toggle = ref(true)
const root = nodeOps.createElement('div')
const app = createApp({
render: () => (toggle.value ? h(Foo) : null)
})
app.mount(root)
expect(serializeInner(root)).toBe('<!---->')
const err = new Error('errored out')
reject!(err)
await timeout()
expect(serializeInner(root)).toBe('errored out')
expect(
'Unhandled error during execution of async component loader'
).toHaveBeenWarned()
toggle.value = false
await nextTick()
expect(serializeInner(root)).toBe('<!---->')
// errored out on previous load, toggle and mock success this time
toggle.value = true
await nextTick()
expect(serializeInner(root)).toBe('<!---->')
// should render this time
resolve!(() => 'resolved')
await timeout()
expect(serializeInner(root)).toBe('resolved')
})
test('error with error + loading components', async () => { test('error with error + loading components', async () => {
let resolve: (comp: Component) => void let resolve: (comp: Component) => void
let reject: (e: Error) => void let reject: (e: Error) => void

View File

@ -117,7 +117,12 @@ export function defineAsyncComponent<
const onError = (err: Error) => { const onError = (err: Error) => {
pendingRequest = null pendingRequest = null
handleError(err, instance, ErrorCodes.ASYNC_COMPONENT_LOADER) handleError(
err,
instance,
ErrorCodes.ASYNC_COMPONENT_LOADER,
!errorComponent /* do not throw in dev if user provided error component */
)
} }
// suspense-controlled or SSR. // suspense-controlled or SSR.
@ -152,7 +157,7 @@ export function defineAsyncComponent<
if (timeout != null) { if (timeout != null) {
setTimeout(() => { setTimeout(() => {
if (!loaded.value) { if (!loaded.value && !error.value) {
const err = new Error( const err = new Error(
`Async component timed out after ${timeout}ms.` `Async component timed out after ${timeout}ms.`
) )

View File

@ -99,7 +99,8 @@ export function callWithAsyncErrorHandling(
export function handleError( export function handleError(
err: unknown, err: unknown,
instance: ComponentInternalInstance | null, instance: ComponentInternalInstance | null,
type: ErrorTypes type: ErrorTypes,
throwInDev = true
) { ) {
const contextVNode = instance ? instance.vnode : null const contextVNode = instance ? instance.vnode : null
if (instance) { if (instance) {
@ -131,10 +132,15 @@ export function handleError(
return return
} }
} }
logError(err, type, contextVNode) logError(err, type, contextVNode, throwInDev)
} }
function logError(err: unknown, type: ErrorTypes, contextVNode: VNode | null) { function logError(
err: unknown,
type: ErrorTypes,
contextVNode: VNode | null,
throwInDev = true
) {
if (__DEV__) { if (__DEV__) {
const info = ErrorTypeStrings[type] const info = ErrorTypeStrings[type]
if (contextVNode) { if (contextVNode) {
@ -144,8 +150,12 @@ function logError(err: unknown, type: ErrorTypes, contextVNode: VNode | null) {
if (contextVNode) { if (contextVNode) {
popWarningContext() popWarningContext()
} }
// crash in dev so it's more noticeable // crash in dev by default so it's more noticeable
throw err if (throwInDev) {
throw err
} else {
console.error(err)
}
} else { } else {
// recover in prod to reduce the impact on end-user // recover in prod to reduce the impact on end-user
console.error(err) console.error(err)