From 72978b4b21c75932bbbae9ad1d9ae692ed1201be Mon Sep 17 00:00:00 2001 From: pete clark Date: Mon, 6 Feb 2023 13:43:25 +0000 Subject: [PATCH 1/5] fix (server-renderer): stop server crashing with unhandled promise rejection. If an async component throws an error whilst another async component is being rendered it results in a unhandled promise rejection and the server crashing. --- packages/server-renderer/src/render.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index a1f327b4320..06dee72541b 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -107,7 +107,11 @@ export function renderComponentVNode( // Note: error display is already done by the wrapped lifecycle hook function. .catch(() => {}) } - return p.then(() => renderComponentSubTree(instance, slotScopeId)) + const renderPromise = p.then(() => renderComponentSubTree(instance, slotScopeId)) + // Note: error display is already done by the wrapped lifecycle hook function. But we must add a catch here to avoid unhandled promise rejection. + renderPromise.catch(() => {}) + + return renderPromise; } else { return renderComponentSubTree(instance, slotScopeId) } From 8ed7322a1581a8bad26a03a33c9dfba56ed19835 Mon Sep 17 00:00:00 2001 From: pete clark Date: Mon, 6 Feb 2023 13:47:43 +0000 Subject: [PATCH 2/5] changes --- packages/server-renderer/src/render.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index 06dee72541b..3950c7333bc 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -107,7 +107,7 @@ export function renderComponentVNode( // Note: error display is already done by the wrapped lifecycle hook function. .catch(() => {}) } - const renderPromise = p.then(() => renderComponentSubTree(instance, slotScopeId)) + const renderPromise = p.then(() => renderComponentSubTree(instance, slotScopeId)) // Note: error display is already done by the wrapped lifecycle hook function. But we must add a catch here to avoid unhandled promise rejection. renderPromise.catch(() => {}) From eb04dc64399a22780eeb46e14e1362bd40a58ff4 Mon Sep 17 00:00:00 2001 From: pete clark Date: Mon, 6 Feb 2023 15:26:55 +0000 Subject: [PATCH 3/5] fix(server-render): Add unit test to cover extra error handling --- .../server-renderer/__tests__/render.spec.ts | 40 +++++++++++++++++++ packages/server-renderer/src/render.ts | 8 ++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/packages/server-renderer/__tests__/render.spec.ts b/packages/server-renderer/__tests__/render.spec.ts index f18001a0ae4..6b1796bcc06 100644 --- a/packages/server-renderer/__tests__/render.spec.ts +++ b/packages/server-renderer/__tests__/render.spec.ts @@ -851,6 +851,46 @@ function testRender(type: string, render: typeof renderToString) { expect(fn2).toBeCalledWith('async child error') }) + test('error handling w/ async setup, multiple component instances and template rendering error', async () => { + const fn = vi.fn() + const fn2 = vi.fn() + + const asyncChildren = defineComponent({ + async setup() {}, + template: `
asyncChildren{{notDefined.value}}
` + }) + const app = createApp({ + name: 'App', + components: { + asyncChildren + }, + template: `
`, + errorCaptured(error) { + fn(error) + } + }) + + app.config.errorHandler = error => { + fn2(error) + } + + let caughtError: any = null + try { + await renderToString(app) + } catch (error) { + caughtError = error + } + + expect(caughtError).not.toBe(null) + expect(caughtError.message).toBe( + "Cannot read property 'value' of undefined" + ) + + expect( + `Property "notDefined" was accessed during render but is not defined on instance` + ).toHaveBeenWarnedTimes(2) + }) + // https://github.com/vuejs/core/issues/3322 test('effect onInvalidate does not error', async () => { const noop = () => {} diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index 3950c7333bc..a82ce8aaf02 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -107,11 +107,13 @@ export function renderComponentVNode( // Note: error display is already done by the wrapped lifecycle hook function. .catch(() => {}) } - const renderPromise = p.then(() => renderComponentSubTree(instance, slotScopeId)) - // Note: error display is already done by the wrapped lifecycle hook function. But we must add a catch here to avoid unhandled promise rejection. + const renderPromise = p.then(() => + renderComponentSubTree(instance, slotScopeId) + ) + // Note: error display is already done by the wrapped lifecycle hook function. But we must add a catch here to avoid unhandled promise rejection. renderPromise.catch(() => {}) - return renderPromise; + return renderPromise } else { return renderComponentSubTree(instance, slotScopeId) } From 6e909076da55a5cb70f0eaeff2d529635d363d50 Mon Sep 17 00:00:00 2001 From: pete clark Date: Mon, 6 Feb 2023 15:33:14 +0000 Subject: [PATCH 4/5] fix(server-render): Change error message in unit test to match expected --- packages/server-renderer/__tests__/render.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server-renderer/__tests__/render.spec.ts b/packages/server-renderer/__tests__/render.spec.ts index 6b1796bcc06..482a46fcf09 100644 --- a/packages/server-renderer/__tests__/render.spec.ts +++ b/packages/server-renderer/__tests__/render.spec.ts @@ -883,7 +883,7 @@ function testRender(type: string, render: typeof renderToString) { expect(caughtError).not.toBe(null) expect(caughtError.message).toBe( - "Cannot read property 'value' of undefined" + "Cannot read properties of undefined (reading 'value')" ) expect( From af729227183dd74e10965d0202bbddc9ca5984c4 Mon Sep 17 00:00:00 2001 From: pete clark Date: Mon, 6 Feb 2023 15:37:31 +0000 Subject: [PATCH 5/5] fix(server-render): Add a few addtional expects to unit test --- packages/server-renderer/__tests__/render.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/server-renderer/__tests__/render.spec.ts b/packages/server-renderer/__tests__/render.spec.ts index 482a46fcf09..c5076534266 100644 --- a/packages/server-renderer/__tests__/render.spec.ts +++ b/packages/server-renderer/__tests__/render.spec.ts @@ -886,6 +886,9 @@ function testRender(type: string, render: typeof renderToString) { "Cannot read properties of undefined (reading 'value')" ) + expect(fn).toHaveBeenCalledTimes(0) + expect(fn2).toHaveBeenCalledTimes(0) + expect( `Property "notDefined" was accessed during render but is not defined on instance` ).toHaveBeenWarnedTimes(2)