diff --git a/.changeset/stupid-baboons-fall.md b/.changeset/stupid-baboons-fall.md new file mode 100644 index 0000000000..66895ad015 --- /dev/null +++ b/.changeset/stupid-baboons-fall.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: abort running obsolete async branches diff --git a/packages/svelte/src/internal/client/dom/blocks/async.js b/packages/svelte/src/internal/client/dom/blocks/async.js index 43af3d8dd3..fd9d0a2964 100644 --- a/packages/svelte/src/internal/client/dom/blocks/async.js +++ b/packages/svelte/src/internal/client/dom/blocks/async.js @@ -49,23 +49,29 @@ export function async(node, blockers = [], expressions = [], fn) { set_hydrate_node(end); } - flatten(blockers, [], expressions, (values) => { - if (was_hydrating) { - set_hydrating(true); - set_hydrate_node(previous_hydrate_node); - } - - try { - // get values eagerly to avoid creating blocks if they reject - for (const d of values) get(d); - - fn(node, ...values); - } finally { + flatten( + blockers, + [], + expressions, + (values) => { if (was_hydrating) { - set_hydrating(false); + set_hydrating(true); + set_hydrate_node(previous_hydrate_node); } - decrement_pending(); - } - }); + try { + // get values eagerly to avoid creating blocks if they reject + for (const d of values) get(d); + + fn(node, ...values); + } finally { + if (was_hydrating) { + set_hydrating(false); + } + + decrement_pending(); + } + }, + () => decrement_pending() + ); } diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index 6aea790c36..134de3a8fc 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -31,8 +31,9 @@ import { aborted } from './effects.js'; * @param {Array<() => any>} sync * @param {Array<() => Promise>} async * @param {(values: Value[]) => any} fn + * @param {() => void} [on_abort] */ -export function flatten(blockers, sync, async, fn) { +export function flatten(blockers, sync, async, fn, on_abort) { const d = is_runes() ? derived : derived_safe_equal; // Filter out already-settled blockers - no need to wait for them @@ -57,12 +58,14 @@ export function flatten(blockers, sync, async, fn) { function finish(values) { restore(); - try { - fn(values); - } catch (error) { - if ((parent.f & DESTROYED) === 0) { + if ((parent.f & DESTROYED) === 0) { + try { + fn(values); + } catch (error) { invoke_error_boundary(error, parent); } + } else { + on_abort?.(); } unset_context(); diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 5af51449ad..01fdd58831 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -239,7 +239,7 @@ export function async_derived(fn, label, location) { recent_async_deriveds.add(signal); setTimeout(() => { - if (recent_async_deriveds.has(signal)) { + if (recent_async_deriveds.has(signal) && (effect.f & DESTROYED) === 0) { w.await_waterfall(/** @type {string} */ (signal.label), location); recent_async_deriveds.delete(signal); } diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 0fad074e6f..e83a49bc60 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -400,13 +400,18 @@ export function deferred_template_effect(fn, sync = [], async = [], blockers = [ var decrement_pending = increment_pending(); } - flatten(blockers, sync, async, (values) => { - create_effect(EFFECT, () => fn(...values.map(get))); - - if (decrement_pending) { - decrement_pending(); + flatten( + blockers, + sync, + async, + (values) => { + create_effect(EFFECT, () => fn(...values.map(get))); + decrement_pending?.(); + }, + () => { + decrement_pending?.(); } - }); + ); } /** diff --git a/packages/svelte/tests/runtime-runes/samples/async-obsolete-branch-no-effect-runs/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-obsolete-branch-no-effect-runs/Child.svelte new file mode 100644 index 0000000000..1d9bdfada2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-obsolete-branch-no-effect-runs/Child.svelte @@ -0,0 +1,6 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/async-obsolete-branch-no-effect-runs/_config.js b/packages/svelte/tests/runtime-runes/samples/async-obsolete-branch-no-effect-runs/_config.js new file mode 100644 index 0000000000..83364706e5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-obsolete-branch-no-effect-runs/_config.js @@ -0,0 +1,27 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs, warnings }) { + const [increment, resolve] = target.querySelectorAll('button'); + + increment.click(); + await tick(); + assert.deepEqual(logs, []); + + resolve.click(); + await tick(); + assert.deepEqual(logs, []); + + resolve.click(); + await tick(); + assert.deepEqual(logs, []); + + resolve.click(); + await tick(); + assert.deepEqual(logs, [1, 2]); + + // no await waterfall / inert derived warnings + assert.deepEqual(warnings, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-obsolete-branch-no-effect-runs/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-obsolete-branch-no-effect-runs/main.svelte new file mode 100644 index 0000000000..fe01ae457e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-obsolete-branch-no-effect-runs/main.svelte @@ -0,0 +1,31 @@ + + + + + + + {#if count % 2 === 0} + {@const double = count * 2} +

true

+ {await push(count)} {double} + + {:else} +

false

+ + {/if} + + {#snippet pending()} +

loading...

+ {/snippet} +