From 2e71f07bc1bab09ca6970b8992d05aeea9b5e9e4 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 26 Mar 2021 11:00:30 -0400 Subject: [PATCH] fix(ssr): ensure async setup error handling work with suspense during ssr --- packages/runtime-core/src/component.ts | 14 ++++++---- .../server-renderer/__tests__/render.spec.ts | 4 +-- .../__tests__/ssrSuspense.spec.ts | 27 ++++++++----------- packages/server-renderer/src/render.ts | 6 +---- 4 files changed, 22 insertions(+), 29 deletions(-) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index fe81efed..1295b759 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -23,7 +23,7 @@ import { } from './componentProps' import { Slots, initSlots, InternalSlots } from './componentSlots' import { warn } from './warning' -import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling' +import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling' import { AppContext, createAppContext, AppConfig } from './apiCreateApp' import { Directive, validateDirectiveName } from './directives' import { @@ -579,7 +579,7 @@ function setupStatefulComponent( currentInstance = instance pauseTracking() - const setupResult = callWithAsyncErrorHandling( + const setupResult = callWithErrorHandling( setup, instance, ErrorCodes.SETUP_FUNCTION, @@ -591,9 +591,13 @@ function setupStatefulComponent( if (isPromise(setupResult)) { if (isSSR) { // return the promise so server-renderer can wait on it - return setupResult.then((resolvedResult: unknown) => { - handleSetupResult(instance, resolvedResult, isSSR) - }) + return setupResult + .then((resolvedResult: unknown) => { + handleSetupResult(instance, resolvedResult, isSSR) + }) + .catch(e => { + handleError(e, instance, ErrorCodes.SETUP_FUNCTION) + }) } else if (__FEATURE_SUSPENSE__) { // async setup returned Promise. // bail here and wait for re-entry. diff --git a/packages/server-renderer/__tests__/render.spec.ts b/packages/server-renderer/__tests__/render.spec.ts index e537d9d7..bb7009f8 100644 --- a/packages/server-renderer/__tests__/render.spec.ts +++ b/packages/server-renderer/__tests__/render.spec.ts @@ -811,10 +811,8 @@ function testRender(type: string, render: typeof renderToString) { expect(fn2).toHaveBeenCalledTimes(1) expect(fn2).toBeCalledWith('async child error') - - expect('Uncaught error in async setup').toHaveBeenWarned() }) - + // https://github.com/vuejs/vue-next/issues/3322 test('effect onInvalidate does not error', async () => { const noop = () => {} diff --git a/packages/server-renderer/__tests__/ssrSuspense.spec.ts b/packages/server-renderer/__tests__/ssrSuspense.spec.ts index 3cdd8137..4da4ce4b 100644 --- a/packages/server-renderer/__tests__/ssrSuspense.spec.ts +++ b/packages/server-renderer/__tests__/ssrSuspense.spec.ts @@ -29,6 +29,7 @@ describe('SSR Suspense', () => { test('reject', async () => { const Comp = { + errorCaptured: jest.fn(() => false), render() { return h(Suspense, null, { default: h(RejectingAsync), @@ -38,10 +39,8 @@ describe('SSR Suspense', () => { } expect(await renderToString(createApp(Comp))).toBe(``) - expect('Uncaught error in async setup').toHaveBeenWarned() - expect( - 'Unhandled error during execution of setup function' - ).toHaveBeenWarned() + + expect(Comp.errorCaptured).toHaveBeenCalledTimes(1) expect('missing template').toHaveBeenWarned() }) @@ -62,6 +61,7 @@ describe('SSR Suspense', () => { test('resolving component + rejecting component', async () => { const Comp = { + errorCaptured: jest.fn(() => false), render() { return h(Suspense, null, { default: h('div', [h(ResolvingAsync), h(RejectingAsync)]), @@ -73,15 +73,14 @@ describe('SSR Suspense', () => { expect(await renderToString(createApp(Comp))).toBe( `
async
` ) - expect('Uncaught error in async setup').toHaveBeenWarned() - expect( - 'Unhandled error during execution of setup function' - ).toHaveBeenWarned() + + expect(Comp.errorCaptured).toHaveBeenCalledTimes(1) expect('missing template or render function').toHaveBeenWarned() }) test('failing suspense in passing suspense', async () => { const Comp = { + errorCaptured: jest.fn(() => false), render() { return h(Suspense, null, { default: h('div', [ @@ -99,15 +98,14 @@ describe('SSR Suspense', () => { expect(await renderToString(createApp(Comp))).toBe( `
async
` ) - expect('Uncaught error in async setup').toHaveBeenWarned() - expect( - 'Unhandled error during execution of setup function' - ).toHaveBeenWarned() + + expect(Comp.errorCaptured).toHaveBeenCalledTimes(1) expect('missing template').toHaveBeenWarned() }) test('passing suspense in failing suspense', async () => { const Comp = { + errorCaptured: jest.fn(() => false), render() { return h(Suspense, null, { default: h('div', [ @@ -125,10 +123,7 @@ describe('SSR Suspense', () => { expect(await renderToString(createApp(Comp))).toBe( `
async
` ) - expect('Uncaught error in async setup').toHaveBeenWarned() - expect( - 'Unhandled error during execution of setup function' - ).toHaveBeenWarned() + expect(Comp.errorCaptured).toHaveBeenCalledTimes(1) expect('missing template').toHaveBeenWarned() }) }) diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index c0ba83be..77a268b5 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -89,11 +89,7 @@ export function renderComponentVNode( const hasAsyncSetup = isPromise(res) const prefetch = (vnode.type as ComponentOptions).serverPrefetch if (hasAsyncSetup || prefetch) { - let p = hasAsyncSetup - ? (res as Promise).catch(err => { - warn(`[@vue/server-renderer]: Uncaught error in async setup:\n`, err) - }) - : Promise.resolve() + let p = hasAsyncSetup ? (res as Promise) : Promise.resolve() if (prefetch) { p = p.then(() => prefetch.call(instance.proxy)).catch(err => { warn(`[@vue/server-renderer]: Uncaught error in serverPrefetch:\n`, err)