restore first_batch?.next check when assigning to a derived in internal_set

Marking the derived clean while other batches are in flight hides changes they haven't seen yet.
async-another-try-pt-1-prune
Rich Harris 2 days ago
parent fa16f59447
commit 336d1ef608
No known key found for this signature in database

@ -37,7 +37,8 @@ import {
eager_block_effects, eager_block_effects,
schedule_effect, schedule_effect,
legacy_updates, legacy_updates,
current_batch current_batch,
first_batch
} from './batch.js'; } from './batch.js';
import { proxy } from '../proxy.js'; import { proxy } from '../proxy.js';
import { execute_derived } from './deriveds.js'; import { execute_derived } from './deriveds.js';
@ -206,7 +207,11 @@ export function internal_set(source, value, updated_during_traversal = null) {
// During time traveling we don't want to reset the status so that // During time traveling we don't want to reset the status so that
// traversal of the graph in the other batches still happens // traversal of the graph in the other batches still happens
if (batch_values === null) { if (
batch_values === null &&
// could also be "read outside of reactivity", e.g. in an event handler
!first_batch?.next
) {
update_derived_status(derived); update_derived_status(derived);
} }
} }

@ -0,0 +1,39 @@
import { tick } from 'svelte';
import { test } from '../../test';
const buttons =
'<button>s</button><button>u</button><button>d</button><button>resolve a</button><button>resolve b</button><button>resolve c</button>';
export default test({
mode: ['client'],
async test({ assert, target, logs }) {
await tick();
const [s, u, d, resolve_a, resolve_b, resolve_c] = target.querySelectorAll('button');
// batch A: `d` becomes 10, but is not evaluated yet (the `<span>` is deferred)
s.click();
await tick();
// batch B
u.click();
await tick();
// batch C overrides `d` while A and B are pending. This must not mark `d` as
// clean, because the other batches haven't seen its new value (10) yet
d.click();
await tick();
// B sees A's change (but not C's), so `d` is 10 in its world
resolve_b.click();
await tick();
assert.equal(logs[0], 'probe 10');
resolve_a.click();
await tick();
assert.htmlEqual(target.innerHTML, `${buttons}<p>1</p><p>0</p><span>10</span><i>10</i>`);
resolve_c.click();
await tick();
assert.htmlEqual(target.innerHTML, `${buttons}<p>1</p><p>1</p><span>99</span><i>99</i>`);
}
});

@ -0,0 +1,42 @@
<script>
let s = $state(0);
let u = $state(0);
let w = $state(0);
let show = $state(true);
let d = $derived(s * 10);
const pending = new Map();
function delay(name, value) {
if (!value) return value;
return new Promise((resolve) => pending.set(name, () => resolve(value)));
}
function probe(value) {
console.log(`probe ${value}`);
return value;
}
</script>
<button onclick={() => s++}>s</button>
<button onclick={() => u++}>u</button>
<button
onclick={() => {
d = 99;
w++;
}}>d</button
>
<button onclick={() => pending.get('a')?.()}>resolve a</button>
<button onclick={() => pending.get('b')?.()}>resolve b</button>
<button onclick={() => pending.get('c')?.()}>resolve c</button>
<p>{await delay('a', s)}</p>
<p>{await delay('c', w)}</p>
<!-- inside a branch, so that B's traversal doesn't reach (and stash) the effect reading `d` -->
{#if show}
<span>{d}</span>
{/if}
{#if await delay('b', u)}
<i>{await probe(d)}</i>
{/if}
Loading…
Cancel
Save