fix: rerun derived that had an abort controller on reconnection (#18551)

Follow-up to #18400 - we need to mark a derived with an abort signal
that is frozen as dirty so it is guaranteed to rerun when it
reconnects/is re-requested. Else you could return a stale value, or
worse, you returned a promise from the derived which you aborted, and
it's now in the rejected state until you update one of its dependencies.
pull/18552/head
Simon H 5 days ago committed by GitHub
parent 602a873b6b
commit 22e0adb75a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: rerun derived that had an abort controller on reconnection

@ -413,6 +413,8 @@ function remove_reaction(signal, dependency) {
without_reactive_context(() => {
/** @type {AbortController} */ (derived.ac).abort(STALE_REACTION);
derived.ac = null;
// ensure it reruns right away next time instead of potentially returning a rejected promise as its value
set_signal_status(derived, DIRTY);
});
}

@ -0,0 +1,32 @@
import { test } from '../../test';
import { tick } from 'svelte';
export default test({
async test({ assert, target }) {
const [increment, toggle, resolve] = target.querySelectorAll('button');
const [div] = target.querySelectorAll('div');
assert.htmlEqual(div.innerHTML, 'loading');
resolve.click();
await tick();
assert.htmlEqual(div.innerHTML, '0');
increment.click();
await tick();
assert.htmlEqual(div.innerHTML, 'loading');
toggle.click();
await tick();
assert.htmlEqual(div.innerHTML, '');
toggle.click();
await tick();
assert.htmlEqual(div.innerHTML, 'loading');
resolve.click(); // this one's for clearing the obsolete/aborted one from the queue
await tick();
resolve.click();
await tick();
assert.htmlEqual(div.innerHTML, '2');
}
});

@ -0,0 +1,32 @@
<script>
import { getAbortSignal } from 'svelte'
let show = $state(true)
let count = $state(0);
let queued = [];
export function sleep(value, signal) {
return new Promise((resolve, reject) => {
signal.addEventListener('abort', reject, { once: true });
queued.push(() => resolve(value));
});
}
const double = $derived(sleep(count * 2, getAbortSignal()));
</script>
<button onclick={() => count += 1}>clicks: {count}</button>
<button onclick={() => show = !show}>toggle</button>
<button onclick={() => queued.shift()?.()}>resolve</button>
<div>
{#if show}
{#await double}
loading
{:then value}
{value}
{:catch}
error
{/await}
{/if}
</div>
Loading…
Cancel
Save