From 66378e51834cf143bd0f84bfe8510babbd54f235 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 14 Aug 2025 13:48:27 +0200 Subject: [PATCH] fix: properly catch top level await errors async errors within the template and derived etc are properly handled because they know about the last active effect and invoke the error boundary correctly as a response. This logic was missing for our top level await output. Fixes #16613 --- .changeset/silent-suns-whisper.md | 5 +++++ .../3-transform/client/transform-client.js | 17 ++++++++++++++--- packages/svelte/src/compiler/utils/builders.js | 18 ++++++++++++++++++ packages/svelte/src/internal/client/index.js | 4 +++- .../src/internal/client/reactivity/effects.js | 3 +-- 5 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 .changeset/silent-suns-whisper.md diff --git a/.changeset/silent-suns-whisper.md b/.changeset/silent-suns-whisper.md new file mode 100644 index 0000000000..7ee7d74abc --- /dev/null +++ b/.changeset/silent-suns-whisper.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: properly catch top level await errors diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 166207f66a..eb32f9c98b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -402,9 +402,20 @@ export function client_component(analysis, options) { params, b.block([ b.var('$$unsuspend', b.call('$.suspend')), - ...component_block.body, - b.if(b.call('$.aborted'), b.return()), - .../** @type {ESTree.Statement[]} */ (template.body), + b.var('$$active', b.id('$.active_effect')), + b.try_catch( + b.block([ + ...component_block.body, + b.if(b.call('$.aborted'), b.return()), + .../** @type {ESTree.Statement[]} */ (template.body) + ]), + b.block([ + b.if( + b.unary('!', b.call('$.aborted', b.id('$$active'))), + b.stmt(b.call('$.invoke_error_boundary', b.id('$$error'), b.id('$$active'))) + ) + ]) + ), b.stmt(b.call('$$unsuspend')) ]), true diff --git a/packages/svelte/src/compiler/utils/builders.js b/packages/svelte/src/compiler/utils/builders.js index 56a5f31ffe..c77cd7eee7 100644 --- a/packages/svelte/src/compiler/utils/builders.js +++ b/packages/svelte/src/compiler/utils/builders.js @@ -659,6 +659,24 @@ export function throw_error(str) { }; } +/** + * @param {ESTree.BlockStatement} body + * @param {ESTree.BlockStatement} handler + * @returns {ESTree.TryStatement} + */ +export function try_catch(body, handler) { + return { + type: 'TryStatement', + block: body, + handler: { + type: 'CatchClause', + param: id('$$error'), + body: handler + }, + finalizer: null + }; +} + export { await_builder as await, let_builder as let, diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index c094c9e044..4089401a7e 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -151,7 +151,8 @@ export { untrack, exclude_from_object, deep_read, - deep_read_state + deep_read_state, + active_effect } from './runtime.js'; export { validate_binding, validate_each_keys } from './validate.js'; export { raf } from './timing.js'; @@ -176,3 +177,4 @@ export { } from '../shared/validate.js'; export { strict_equals, equals } from './dev/equality.js'; export { log_if_contains_state } from './dev/console-log.js'; +export { invoke_error_boundary } from './error-handling.js'; diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 68a1555032..df3dd75808 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -648,7 +648,6 @@ function resume_children(effect, local) { } } -export function aborted() { - var effect = /** @type {Effect} */ (active_effect); +export function aborted(effect = /** @type {Effect} */ (active_effect)) { return (effect.f & DESTROYED) !== 0; }