fix: handle asnyc updates within pending boundary (#17873)

When an async value is updated inside the boundary while the pending
snippet is shown, we previously didn't notice that update and instead
showed an outdated value once it resolved. This fixes that by rejecting
all deferreds inside an async_derived while the pending snippet is
shown.

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/17877/head
Simon H 3 days ago committed by GitHub
parent 0206a2019e
commit 2deebdea8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: handle asnyc updates within pending boundary

@ -146,8 +146,18 @@ export function async_derived(fn, label, location) {
if (should_suspend) {
var decrement_pending = increment_pending();
deferreds.get(batch)?.reject(STALE_REACTION);
deferreds.delete(batch); // delete to ensure correct order in Map iteration below
if (/** @type {Boundary} */ (parent.b).is_rendered()) {
deferreds.get(batch)?.reject(STALE_REACTION);
deferreds.delete(batch); // delete to ensure correct order in Map iteration below
} else {
// While the boundary is still showing pending, a new run supersedes all older in-flight runs
// for this async expression. Cancel eagerly so resolution cannot commit stale values.
for (const d of deferreds.values()) {
d.reject(STALE_REACTION);
}
deferreds.clear();
}
deferreds.set(batch, d);
}

@ -0,0 +1,51 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target }) {
await tick();
const [shift, increment] = target.querySelectorAll('button');
assert.htmlEqual(
target.innerHTML,
`
<button>shift</button>
<button>increment</button>
loading
`
);
increment.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>shift</button>
<button>increment</button>
loading
`
);
shift.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>shift</button>
<button>increment</button>
loading
`
);
shift.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>shift</button>
<button>increment</button>
1
`
);
}
});

@ -0,0 +1,19 @@
<script>
let queue = [];
function push(value) {
const deferred = Promise.withResolvers();
queue.push(() => deferred.resolve(value));
return deferred.promise;
}
let count = $state(0);
</script>
<button onclick={() => queue.shift()()}>shift</button>
<button onclick={() => count++}>increment</button>
<svelte:boundary>
{await push(count)}
{#snippet pending()}loading{/snippet}
</svelte:boundary>
Loading…
Cancel
Save