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 @@