diff --git a/.changeset/fresh-stars-grin.md b/.changeset/fresh-stars-grin.md new file mode 100644 index 0000000000..3d56792d1e --- /dev/null +++ b/.changeset/fresh-stars-grin.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: rethrow error of failed iterable after calling `return()` diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index 6aea790c36..7692b41248 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -215,6 +215,9 @@ export async function* for_await_track_reactivity_loss(iterable) { /** Whether the completion of the iterator was "normal", meaning it wasn't ended via `break` or a similar method */ let normal_completion = false; + const no_error = {}; // because a madman might throw `null` or `undefined` + /** @type {any} */ + let thrown_error = no_error; try { while (true) { const { done, value } = (await track_reactivity_loss(iterator.next()))(); @@ -226,11 +229,29 @@ export async function* for_await_track_reactivity_loss(iterable) { yield value; set_reactivity_loss_tracker(prev); } + } catch (error) { + thrown_error = error; } finally { // If the iterator had an abrupt completion and `return` is defined on the iterator, call it and return the value if (!normal_completion && iterator.return !== undefined) { - // eslint-disable-next-line no-unsafe-finally - return /** @type {TReturn} */ ((await track_reactivity_loss(iterator.return()))().value); + try { + // Spec says a non-normal completion is break, return, or throw, in which case iterator.return() should be called... + const result = (await track_reactivity_loss(iterator.return()))(); + + // ... but only in case of break or return the result is returned, otherwise error takes precedence and is (re)thrown + if (thrown_error === no_error) { + // eslint-disable-next-line no-unsafe-finally + return /** @type {TReturn} */ (result.value); + } + } catch (error) { + if (thrown_error === no_error) { + throw error; + } + } + } + + if (thrown_error !== no_error) { + throw thrown_error; } } } diff --git a/packages/svelte/tests/runtime-runes/samples/async-reactivity-loss-for-await-throws/_config.js b/packages/svelte/tests/runtime-runes/samples/async-reactivity-loss-for-await-throws/_config.js new file mode 100644 index 0000000000..9785f639cb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-reactivity-loss-for-await-throws/_config.js @@ -0,0 +1,24 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; +import { normalise_trace_logs } from '../../../helpers.js'; + +export default test({ + compileOptions: { + dev: true + }, + html: '
pending
', + async test({ assert, target, warnings }) { + await tick(); + + assert.htmlEqual( + target.innerHTML, + 'pending
+ {/snippet} +