prevent more overfiring

async-another-try-pt-1
Simon Holthausen 7 days ago
parent e19a7530da
commit 2a167c8ddb
No known key found for this signature in database

@ -1291,6 +1291,7 @@ export function eager(fn) {
let parent = active_reaction;
let version = version_map.get(parent) ?? source(0);
version.f |= EAGER_EFFECT;
version_map.set(parent, version);
if (DEV) {
@ -1308,12 +1309,15 @@ export function eager(fn) {
// the first time this runs, we create an eager effect
// that will run eagerly whenever the expression changes
var previous_batch_values = batch_values;
var previous_stale_sources = stale_sources;
try {
batch_values = null;
stale_sources = null;
value = fn();
} finally {
batch_values = previous_batch_values;
stale_sources = previous_stale_sources;
}
return;

@ -354,14 +354,15 @@ export function increment(source) {
* Make `reaction` re-run in the current batch. For a derived this means dirtying
* its reactions, as if the derived's value had changed.
* @param {Reaction} reaction
* @param {number} status
*/
export function invalidate(reaction) {
set_signal_status(reaction, DIRTY);
export function invalidate(reaction, status) {
set_signal_status(reaction, status);
if ((reaction.f & DERIVED) !== 0) {
seen = null;
count_deps = 0;
mark_reactions(/** @type {Derived} */ (reaction), DIRTY, null);
mark_reactions(/** @type {Derived} */ (reaction), status, null);
seen = null;
} else {
schedule_effect(/** @type {Effect} */ (reaction));

@ -23,7 +23,8 @@ import {
ERROR_VALUE,
MANAGED_EFFECT,
REACTION_RAN,
ASYNC
ASYNC,
EAGER_EFFECT
} from './constants.js';
import { invalidate, old_values } from './reactivity/sources.js';
import {
@ -790,17 +791,20 @@ function get_batch_value(signal, first_time) {
batch.stale_readers.add(reader);
if (current.is_eager) {
// TODO only do this if we can see that the batch doesn't have this already scheduled in (maybe)dirty effects.
batch.oncommit(() => {
batch.stale_readers.delete(reader);
Batch.ensure();
invalidate(reader);
});
// Reactions of eager effects with at least one non-eager effect (which we know is true here due to the stale_sources check above)
// need to rerun because they could do something like `$state.eager(count) !== count`.
if (reader.deps?.some((dep) => dep.f & EAGER_EFFECT)) {
batch.oncommit(() => {
batch.stale_readers.delete(reader);
Batch.ensure();
invalidate(reader, DIRTY);
});
}
} else {
queue_micro_task(() => {
batch.stale_readers.delete(reader);
const b = batch.activate();
invalidate(reader);
invalidate(reader, MAYBE_DIRTY);
b.flush();
});
}

@ -0,0 +1,38 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
await tick();
const [increment_a, increment_a_b, shift_second, shift] = target.querySelectorAll('button');
const [p] = target.querySelectorAll('p');
increment_a.click();
await tick();
assert.deepEqual(logs, ['a_d: 1']);
logs.length = 0;
increment_a_b.click();
await tick();
assert.deepEqual(logs, ['a_d: 2', 'c: 1']);
logs.length = 0;
shift_second.click(); // a_d of second batch
await tick();
assert.deepEqual(logs, ['c: 3']);
logs.length = 0;
shift.click(); // a_d of first batch - should not rerun second batch
await tick();
assert.deepEqual(logs, ['c: 1']);
logs.length = 0;
shift.click();
await tick();
shift.click();
await tick();
assert.htmlEqual(p.innerHTML, '3');
assert.deepEqual(logs, []);
}
});

@ -0,0 +1,21 @@
<script>
let a = $state(0);
let b = $state(0);
let a_d = $derived(await delay(a, 'a_d'));
let c = $derived(await delay(a_d + b, 'c'));
function delay(value, source) {
if (!value) return value;
console.log(source + ': ' + value);
return new Promise((resolve) => deferred.push(() => resolve(value)));
}
let deferred = [];
</script>
<button onclick={() => a++}>increment a</button>
<button onclick={() => {a++; b++}}>increment a & b</button>
<button onclick={() => deferred.splice(1,1)[0]?.()}>shift second</button>
<button onclick={() => deferred.shift()?.()}>shift</button>
<p>{c}</p>

@ -2,7 +2,7 @@ import { tick } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target }) {
async test({ assert, target, logs }) {
await tick();
const [increment, resolve] = target.querySelectorAll('button');
@ -10,14 +10,15 @@ export default test({
await tick();
assert.htmlEqual(
target.innerHTML,
'<button>increment</button> <button>resolve</button> 0 <p>loading...</p>'
'<button>increment</button> <button>resolve</button> 0 <p>loading...</p> <p>1</p>' // <p>0</p> would also be ok here
);
resolve.click();
await tick();
assert.htmlEqual(
target.innerHTML,
'<button>increment</button> <button>resolve</button> 1 <p>1</p>'
'<button>increment</button> <button>resolve</button> 1 <p>1</p> <p>1</p>'
);
assert.deepEqual(logs, [0, 1]);
}
});

@ -13,8 +13,13 @@
<button onclick={() => queued.shift()?.()}>resolve</button>
{await delay(count)}
{#if $state.eager(count) !== count}
<p>loading...</p>
{:else}
<p>{count}</p>
{/if}
{#if $state.eager(console.log(count) || count)}
<p>{count}</p>
{/if}

Loading…
Cancel
Save