fix: keep flushing new eager effects (#18102)

The previous code "swallowed" new additions to the array of eager
effects that happened while flushing since `eager_flush` did not clear
the array before running, only afterwards. Now it clears the beforehand,
causing newly added eager effects to run, too.

An example where this can happen is `$state.eager` and `$effect.pending`
in combination: first `$state.eager` is flushed, then due to `flushSync`
the `queue_micro_task` inside `boundary.js` that flushes
`$effect.pending` is triggered synchronously, adding new entries to the
`eager_versions` array. If they're only cleared at the end of
`eager_flush`, new entries are swallowed.

Related to #18095 (but not fixing it yet)
pull/18111/head
Simon H 3 months ago committed by GitHub
parent 4a50e8ea3b
commit 273f1a85a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: keep flushing new eager effects

@ -1074,15 +1074,13 @@ export function schedule_effect(effect) {
let eager_versions = [];
function eager_flush() {
try {
flushSync(() => {
for (const version of eager_versions) {
update(version);
}
});
} finally {
flushSync(() => {
const eager = eager_versions;
eager_versions = [];
}
for (const version of eager) {
update(version);
}
});
}
/**

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

@ -0,0 +1,27 @@
<script>
let value = $state(0);
let queued = [];
function delayed(v) {
if (!v) return v;
return new Promise((resolve) => {
queued.push(() => resolve(v));
});
}
</script>
<button onclick={() => value++}>increment</button>
<button onclick={() => queued.shift()?.()}>shift</button>
{$state.eager(value)}
{#if 1}
<p>pending: {$effect.pending()}</p>
{@const tmp = await delayed(value)}
{#if $effect.pending() > 0}
<p>loading...</p>
{:else}
<p>{tmp}</p>
{/if}
{/if}
Loading…
Cancel
Save