瀏覽代碼

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

fix #2129
Evan You 5 年之前
父節點
當前提交
c7b4a379cf

+ 45 - 0
packages/runtime-core/__tests__/apiAsyncComponent.spec.ts

@@ -206,6 +206,51 @@ describe('api: defineAsyncComponent', () => {
     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 () => {
     let resolve: (comp: Component) => void
     let reject: (e: Error) => void

+ 7 - 2
packages/runtime-core/src/apiAsyncComponent.ts

@@ -117,7 +117,12 @@ export function defineAsyncComponent<
 
       const onError = (err: Error) => {
         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.
@@ -152,7 +157,7 @@ export function defineAsyncComponent<
 
       if (timeout != null) {
         setTimeout(() => {
-          if (!loaded.value) {
+          if (!loaded.value && !error.value) {
             const err = new Error(
               `Async component timed out after ${timeout}ms.`
             )

+ 15 - 5
packages/runtime-core/src/errorHandling.ts

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