Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/major-beans-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: unset context on stale promises
6 changes: 3 additions & 3 deletions packages/svelte/src/internal/client/reactivity/deriveds.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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'));
Expand Down
Original file line number Diff line number Diff line change
@@ -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, '<input> 1 | ');

const [input] = target.querySelectorAll('input');

input.value = '1';
input.dispatchEvent(new Event('input', { bubbles: true }));
await macrotask();
assert.htmlEqual(target.innerHTML, '<input> 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, '<input> 5 | 12');
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script>
let count = $state(0);
let value = $state('');
let prev;

function asd(v) {
const r = Promise.withResolvers();

if (prev || v === '') {
Promise.resolve().then(async () => {
count++;
r.resolve(v);
await new Promise(r => setTimeout(r, 0));
// TODO with a microtask like below it still throws a mutation error
// await Promise.resolve();
prev?.resolve();
});
} else {
prev = Promise.withResolvers();
prev.promise.then(() => {
count++;
r.resolve(v)
});
}

return r.promise;
}

const x = $derived(await asd(value))
</script>

<input bind:value />

{count} | {x}
Loading