diff --git a/.changeset/major-beans-fry.md b/.changeset/major-beans-fry.md
new file mode 100644
index 000000000000..8f35683cd623
--- /dev/null
+++ b/.changeset/major-beans-fry.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: unset context on stale promises
diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js
index 5d5976a6c115..076a91923680 100644
--- a/packages/svelte/src/internal/client/reactivity/deriveds.js
+++ b/packages/svelte/src/internal/client/reactivity/deriveds.js
@@ -126,9 +126,11 @@ export function async_derived(fn, location) {
try {
// 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.
- Promise.resolve(fn()).then(d.resolve, d.reject);
+ // We call `unset_context` to undo any `save` calls that happen inside `fn()`
+ Promise.resolve(fn()).then(d.resolve, d.reject).then(unset_context);
} catch (error) {
d.reject(error);
+ unset_context();
}
if (DEV) current_async_effect = null;
@@ -185,8 +187,6 @@ export function async_derived(fn, location) {
boundary.update_pending_count(-1);
if (!pending) batch.decrement();
}
-
- unset_context();
};
d.promise.then(handler, (e) => handler(null, e || 'unknown'));
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
new file mode 100644
index 000000000000..bccf12562ad3
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js
@@ -0,0 +1,26 @@
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target }) {
+ // We gotta wait a bit more in this test because of the macrotasks in App.svelte
+ function macrotask(t = 3) {
+ return new Promise((r) => setTimeout(r, t));
+ }
+
+ await macrotask();
+ assert.htmlEqual(target.innerHTML, ' 1 | ');
+
+ const [input] = target.querySelectorAll('input');
+
+ input.value = '1';
+ input.dispatchEvent(new Event('input', { bubbles: true }));
+ await macrotask();
+ assert.htmlEqual(target.innerHTML, ' 1 | ');
+
+ input.value = '12';
+ input.dispatchEvent(new Event('input', { bubbles: true }));
+ await macrotask(6);
+ // TODO this is wrong (separate bug), this should be 3 | 12
+ assert.htmlEqual(target.innerHTML, ' 5 | 12');
+ }
+});
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
new file mode 100644
index 000000000000..dc4a157928a3
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/main.svelte
@@ -0,0 +1,34 @@
+
+
+
+
+{count} | {x}