async-another-try
Simon Holthausen 4 days ago
parent 3b92502f00
commit 494c8b9f08
No known key found for this signature in database

@ -80,7 +80,8 @@ export let wv_values = null;
export let held_sources = null;
/**
* Sources where the current batch reads an outdated value not in its own `current` map. Used to discover dependencies between batches.
* Sources where the current batch reads an outdated value of a later batch (or if this is an eager batch, any other batch) not in its own `current` map.
* Used to discover dependencies between batches.
* @type {Map<Value, Batch> | null}
*/
export let stale_sources = null;
@ -240,8 +241,8 @@ export class Batch {
#skipped_branches = new Map();
/**
* Inverse of #skipped_branches which we need to tell prior batches to unskip them when committing
* true indicates that this branch is new to the eyes of this fork but was already created before.
* Inverse of #skipped_branches which we need to tell prior batches to unskip them when committing.
* `true` indicates that this branch is new to the eyes of this fork but was already created before.
* @type {Map<Effect, boolean>}
*/
unskipped_branches = new Map();
@ -766,15 +767,23 @@ export class Batch {
wv_values?.set(source, wv);
}
// The value becomes the real one unless this is a fork or a later batch wrote to the source
// as well. For a derived, the same goes if a later batch wrote to one of its dependencies:
// the derived value then belongs to that batch's world, not ours. (This is deliberately not
// the same check as in `update_effect`: a later batch's write is visible through `batch_values`,
// A derived computed from inputs that differ from the real values must stay batch-local.
// This also happens when committing a later batch hides an earlier batch's pending writes.
let is_latest_value =
!this.is_fork &&
(!is_derived ||
!(
/** @type {Derived} */ (source).deps?.some(
(d) => batch_values?.has(d) && batch_values.get(d) !== d.v
)
));
// A later batch may also own a newer value of the source or one of a derived's dependencies.
// The check above isn't sufficient here: a later batch's write is visible through `batch_values`,
// so comparing what was read against the real value could not attribute the value to the right
// batch, see `async-dont-rebase-new-batch-4`.) We only need to look one level deep: `is_dirty`
// batch, see `async-dont-rebase-new-batch-4`. We only need to look one level deep: `is_dirty`
// evaluates the top-most deriveds first, so a dependency derived that was itself not the latest
// value was not written to the real world, and differs from our value for it.
let is_latest_value = !this.is_fork;
for (let batch = this.next; batch !== null && is_latest_value; batch = batch.next) {
if (batch.is_fork) continue;

@ -0,0 +1,36 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
await tick();
const [a, b, resolve, read] = target.querySelectorAll('button');
const [p] = target.querySelectorAll('p');
assert.htmlEqual(p.innerHTML, '0 0 0');
// A waits for its async expression, so its updated input is not yet rendered.
a.click();
await tick();
assert.htmlEqual(p.innerHTML, '0 0 0');
// B commits with A's old input. Its derived values must remain batch-local.
b.click();
await tick();
assert.htmlEqual(p.innerHTML, '0 1 2');
// Outside reactivity, both deriveds should reflect the real inputs, without
// changing the DOM until A resolves.
read.click();
await tick();
assert.htmlEqual(p.innerHTML, '0 1 2');
resolve.click();
await tick();
assert.htmlEqual(p.innerHTML, '1 2 4');
read.click();
assert.deepEqual(logs, [
{ a: 1, b: 1, c: 2, doubled: 4 },
{ a: 1, b: 1, c: 2, doubled: 4 }
]);
}
});

@ -0,0 +1,18 @@
<script>
let a = $state(0);
let b = $state(0);
const c = $derived(a + b);
const doubled = $derived(c * 2);
const queued = [];
function delay(value) {
if (!value) return value;
return new Promise((resolve) => queued.push(() => resolve(value)));
}
</script>
<p>{await delay(a)} {c} {doubled}</p>
<button onclick={() => a++}>a</button>
<button onclick={() => b++}>b</button>
<button onclick={() => queued.shift()?.()}>resolve</button>
<button onclick={() => console.log({ a, b, c, doubled })}>read</button>
Loading…
Cancel
Save