Skip to content

Commit 677af57

Browse files
authored
fix: ensure async deriveds always get dependencies from thennable (#16672)
When an async derived already has a previous promise that is still pending, we were not accessing the `then` property of the new promise. If that property access causes signals to be read, that meant that those dependencies were lost and as such the derived wouldn't rerun anymore when it should. The fix is to make sure to always access the thennable.
1 parent 967431c commit 677af57

File tree

4 files changed

+84
-0
lines changed

4 files changed

+84
-0
lines changed

.changeset/orange-chefs-float.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: ensure async deriveds always get dependencies from thennable

packages/svelte/src/internal/client/reactivity/deriveds.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ export function async_derived(fn, location) {
120120

121121
try {
122122
var p = fn();
123+
// Make sure to always access the then property to read any signals
124+
// it might access, so that we track them as dependencies.
125+
if (prev) Promise.resolve(p).catch(() => {}); // avoid unhandled rejection
123126
} catch (error) {
124127
p = Promise.reject(error);
125128
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { tick } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, target }) {
6+
const [increment, pop] = target.querySelectorAll('button');
7+
8+
increment.click();
9+
await tick();
10+
11+
pop.click();
12+
await tick();
13+
14+
pop.click();
15+
await tick();
16+
17+
assert.htmlEqual(
18+
target.innerHTML,
19+
`
20+
<button>increment</button>
21+
<button>pop</button>
22+
<p>1</p>
23+
`
24+
);
25+
26+
increment.click();
27+
await tick();
28+
29+
pop.click();
30+
await tick();
31+
32+
assert.htmlEqual(
33+
target.innerHTML,
34+
`
35+
<button>increment</button>
36+
<button>pop</button>
37+
<p>2</p>
38+
`
39+
);
40+
}
41+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
<script>
3+
let count = $state(0);
4+
5+
let deferreds = [];
6+
7+
class X {
8+
constructor(promise) {
9+
this.promise = promise;
10+
}
11+
12+
get then() {
13+
count;
14+
15+
return (resolve) => this.promise.then(() => count).then(resolve)
16+
}
17+
}
18+
19+
function push() {
20+
const deferred = Promise.withResolvers();
21+
deferreds.push(deferred);
22+
return new X(deferred.promise);
23+
}
24+
</script>
25+
26+
<button onclick={() => count += 1}>increment</button>
27+
<button onclick={() => deferreds.pop()?.resolve(count)}>pop</button>
28+
29+
<svelte:boundary>
30+
<p>{await push()}</p>
31+
32+
{#snippet pending()}
33+
<p>loading...</p>
34+
{/snippet}
35+
</svelte:boundary>

0 commit comments

Comments
 (0)