diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index 24ff4793ea..a308868336 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -1,5 +1,5 @@ /** @import { Effect, TemplateNode, Value } from '#client' */ -import { DESTROYED } from '#client/constants'; +import { DESTROYED, STALE_REACTION } from '#client/constants'; import { DEV } from 'esm-env'; import { component_context, diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 5414dd2a54..91635bd5d2 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -76,6 +76,8 @@ let is_flushing = false; export let is_flushing_sync = false; export class Batch { + committed = false; + /** * The current values of any sources that are updated in this batch * They keys of this map are identical to `this.#previous` @@ -399,6 +401,7 @@ export class Batch { batch_values = previous_batch_values; } + this.committed = true; batches.delete(this); this.#deferred?.resolve(); diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 1989220abe..06ae0f6d7a 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -127,7 +127,17 @@ export function async_derived(fn, location) { // If this code is changed at some point, make sure to still access the then property // of fn() to read any signals it might access, so that we track them as dependencies. // We call `unset_context` to undo any `save` calls that happen inside `fn()` - Promise.resolve(fn()).then(d.resolve, d.reject).then(unset_context); + Promise.resolve(fn()) + .then(d.resolve, d.reject) + .then(() => { + if (batch === current_batch && batch.committed) { + // if the batch was rejected as stale, we need to cleanup + // after any `$.save(...)` calls inside `fn()` + batch.deactivate(); + } + + unset_context(); + }); } catch (error) { d.reject(error); unset_context(); diff --git a/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js index 50bb414afc..c02abb59c6 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js @@ -21,5 +21,9 @@ export default test({ input.dispatchEvent(new Event('input', { bubbles: true })); await macrotask(6); assert.htmlEqual(target.innerHTML, ' 3 | 12'); + input.value = ''; + input.dispatchEvent(new Event('input', { bubbles: true })); + await macrotask(); + assert.htmlEqual(target.innerHTML, ' 4 | '); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/main.svelte index dc4a157928..2a36942ff2 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/main.svelte @@ -1,28 +1,32 @@