diff --git a/.changeset/nasty-friends-crash.md b/.changeset/nasty-friends-crash.md new file mode 100644 index 0000000000..5895f3752a --- /dev/null +++ b/.changeset/nasty-friends-crash.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: handle asnyc updates within pending boundary diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index c1ee4f3f52..d8989ef03d 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -146,8 +146,18 @@ export function async_derived(fn, label, location) { if (should_suspend) { var decrement_pending = increment_pending(); - deferreds.get(batch)?.reject(STALE_REACTION); - deferreds.delete(batch); // delete to ensure correct order in Map iteration below + if (/** @type {Boundary} */ (parent.b).is_rendered()) { + deferreds.get(batch)?.reject(STALE_REACTION); + deferreds.delete(batch); // delete to ensure correct order in Map iteration below + } else { + // While the boundary is still showing pending, a new run supersedes all older in-flight runs + // for this async expression. Cancel eagerly so resolution cannot commit stale values. + for (const d of deferreds.values()) { + d.reject(STALE_REACTION); + } + deferreds.clear(); + } + deferreds.set(batch, d); } diff --git a/packages/svelte/tests/runtime-runes/samples/async-boundary-update-while-pending/_config.js b/packages/svelte/tests/runtime-runes/samples/async-boundary-update-while-pending/_config.js new file mode 100644 index 0000000000..e444aa8f9b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-boundary-update-while-pending/_config.js @@ -0,0 +1,51 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + await tick(); + const [shift, increment] = target.querySelectorAll('button'); + + assert.htmlEqual( + target.innerHTML, + ` + + + loading + ` + ); + + increment.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + + loading + ` + ); + + shift.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + + loading + ` + ); + + shift.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + + 1 + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-boundary-update-while-pending/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-boundary-update-while-pending/main.svelte new file mode 100644 index 0000000000..c5a32dc4b9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-boundary-update-while-pending/main.svelte @@ -0,0 +1,19 @@ + + + + + + + {await push(count)} + {#snippet pending()}loading{/snippet} +