Skip to content

Commit 60eaa28

Browse files
committed
don't commit stale batches
1 parent 39a7f08 commit 60eaa28

File tree

4 files changed

+95
-2
lines changed

4 files changed

+95
-2
lines changed

packages/svelte/src/internal/client/reactivity/batch.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ export class Batch {
8787
*/
8888
#deferred = null;
8989

90+
/**
91+
* True if an async effect inside this batch resolved and
92+
* its parent branch was already deleted
93+
*/
94+
#neutered = false;
95+
9096
/**
9197
* Async effects (created inside `async_derived`) encountered during processing.
9298
* These run after the rest of the batch has updated, since they should
@@ -278,10 +284,14 @@ export class Batch {
278284
current_batch = null;
279285
}
280286

287+
neuter() {
288+
this.#neutered = true;
289+
}
290+
281291
flush() {
282292
if (queued_root_effects.length > 0) {
283293
this.flush_effects();
284-
} else {
294+
} else if (!this.#neutered) {
285295
this.#commit();
286296
}
287297

packages/svelte/src/internal/client/reactivity/deriveds.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
EFFECT_PRESERVED,
1010
MAYBE_DIRTY,
1111
STALE_REACTION,
12-
UNOWNED
12+
UNOWNED,
13+
DESTROYED
1314
} from '#client/constants';
1415
import {
1516
active_reaction,
@@ -144,6 +145,10 @@ export function async_derived(fn, location) {
144145
const handler = (value, error = undefined) => {
145146
prev = null;
146147

148+
if ((parent.f & DESTROYED) !== 0) {
149+
batch.neuter();
150+
}
151+
147152
current_async_effect = null;
148153

149154
if (!pending) batch.activate();
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { tick } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, target }) {
6+
await tick();
7+
8+
const [increment, shift] = target.querySelectorAll('button');
9+
10+
assert.htmlEqual(
11+
target.innerHTML,
12+
`
13+
<button>increment</button>
14+
<button>shift</button>
15+
<p>0</p>
16+
`
17+
);
18+
19+
increment.click();
20+
await tick();
21+
22+
increment.click();
23+
await tick();
24+
25+
increment.click();
26+
await tick();
27+
28+
shift.click();
29+
await tick();
30+
31+
assert.htmlEqual(
32+
target.innerHTML,
33+
`
34+
<button>increment</button>
35+
<button>shift</button>
36+
<p>2</p>
37+
`
38+
);
39+
40+
shift.click();
41+
await tick();
42+
43+
assert.htmlEqual(
44+
target.innerHTML,
45+
`
46+
<button>increment</button>
47+
<button>shift</button>
48+
<p>delayed: 3</p>
49+
`
50+
);
51+
}
52+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<script lang="ts">
2+
let count = $state(0);
3+
4+
let deferreds = [];
5+
6+
function push() {
7+
const deferred = Promise.withResolvers();
8+
deferreds.push(deferred);
9+
return deferred.promise;
10+
}
11+
</script>
12+
13+
<button onclick={() => count += 1}>increment</button>
14+
<button onclick={() => deferreds.shift()?.resolve(count)}>shift</button>
15+
16+
<svelte:boundary>
17+
{#if count % 2}
18+
<p>delayed: {await push()}</p>
19+
{:else}
20+
<p>{await count}</p>
21+
{/if}
22+
23+
{#snippet pending()}
24+
<p>loading...</p>
25+
{/snippet}
26+
</svelte:boundary>

0 commit comments

Comments
 (0)