detect async_deriveds inside batches that are destroyed in later batches

boundary-batch-first-run
Simon Holthausen 1 day ago
parent 3d731c7d17
commit f8747b9774

@ -344,6 +344,48 @@ export class Batch {
current_batch = this; current_batch = this;
} }
/**
* Check if the branch this effect is in is obsolete in a later batch.
* That is, if the branch exists in this batch but will be destroyed in a later batch.
* @param {Effect} effect
*/
branch_obsolete(effect) {
/** @type {Effect[]} */
let alive = [];
/** @type {Effect[]} */
let skipped = [];
/** @type {Effect | null} */
let current = effect;
while (current !== null) {
if ((current.f & (BRANCH_EFFECT | ROOT_EFFECT)) !== 0) {
alive.push(current);
if (this.skipped_effects.has(current)) {
skipped.push(...alive);
alive = [];
}
}
current = current.parent;
}
let check = false;
for (const b of batches) {
if (b === this) {
check = true;
} else if (check) {
if (
alive.some((branch) => b.skipped_effects.has(branch)) ||
// TODO do we even have to check skipped here? how would an async_derived run for a branch that was already skipped?
(skipped.length > 0 && !skipped.some((branch) => b.skipped_effects.has(branch)))
) {
return true;
}
}
}
return false;
}
deactivate() { deactivate() {
current_batch = null; current_batch = null;
previous_batch = null; previous_batch = null;

@ -115,7 +115,7 @@ export function async_derived(fn, location) {
// only suspend in async deriveds created on initialisation // only suspend in async deriveds created on initialisation
var should_suspend = !active_reaction; var should_suspend = !active_reaction;
async_effect(() => { var effect = async_effect(() => {
if (DEV) current_async_effect = active_effect; if (DEV) current_async_effect = active_effect;
try { try {
@ -153,7 +153,7 @@ export function async_derived(fn, location) {
batch.activate(); batch.activate();
if (error) { if (error) {
if (error !== STALE_REACTION) { if (error !== STALE_REACTION && !batch.branch_obsolete(effect)) {
signal.f |= ERROR_VALUE; signal.f |= ERROR_VALUE;
// @ts-expect-error the error is the wrong type, but we don't care // @ts-expect-error the error is the wrong type, but we don't care

@ -8,9 +8,11 @@ export default test({
increment.click(); increment.click();
await tick(); await tick();
reject.click();
reject.click(); reject.click();
await tick(); await tick();
resolve.click();
resolve.click(); resolve.click();
await tick(); await tick();
@ -22,6 +24,35 @@ export default test({
<button>reject</button> <button>reject</button>
<p>false</p> <p>false</p>
<p>1</p> <p>1</p>
<p>false</p>
<p>1</p>
`
);
increment.click();
await tick();
increment.click();
await tick();
reject.click();
reject.click();
await tick();
resolve.click();
resolve.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>increment</button>
<button>resolve</button>
<button>reject</button>
<p>false</p>
<p>3</p>
<p>false</p>
<p>3</p>
` `
); );
} }

@ -3,7 +3,7 @@
let deferreds = []; let deferreds = [];
function push() { function push(_just_so_that_template_is_reactive_) {
const deferred = Promise.withResolvers(); const deferred = Promise.withResolvers();
deferreds.push(deferred); deferreds.push(deferred);
return deferred.promise; return deferred.promise;
@ -17,10 +17,19 @@
<svelte:boundary> <svelte:boundary>
{#if count % 2 === 0} {#if count % 2 === 0}
<p>true</p> <p>true</p>
{#each await push() as count}<p>{count}</p>{/each} {#each await push(count) as count}<p>{count}</p>{/each}
{:else} {:else}
<p>false</p> <p>false</p>
{#each await push() as count}<p>{count}</p>{/each} {#each await push(count) as count}<p>{count}</p>{/each}
{/if}
{#if count % 2 === 0}
<p>true</p>
{#each await push(count) as count}<p>{count}</p>{/each}
{/if}
{#if count % 2 === 1}
<p>false</p>
{#each await push(count) as count}<p>{count}</p>{/each}
{/if} {/if}
{#snippet pending()} {#snippet pending()}

@ -3,6 +3,11 @@
export let route = $state({ current: 'home' }); export let route = $state({ current: 'home' });
</script> </script>
<script>
// reset from earlier tests
route.current = 'home'
</script>
<button onclick={() => route.reject()}>reject</button> <button onclick={() => route.reject()}>reject</button>
<svelte:boundary> <svelte:boundary>

Loading…
Cancel
Save