Skip to content

Commit 7c194ac

Browse files
committed
ensure immediate flushing on pending boundaries
1 parent f8747b9 commit 7c194ac

File tree

4 files changed

+126
-6
lines changed

4 files changed

+126
-6
lines changed

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -699,14 +699,25 @@ export function schedule_effect(signal) {
699699
export function suspend() {
700700
var boundary = get_pending_boundary();
701701
var batch = /** @type {Batch} */ (current_batch);
702+
// In case the pending snippet is shown, we want to update the UI immediately
703+
// and not have the batch be blocked on async work,
704+
// since the async work is happening "hidden" behind the pending snippet.
705+
var ignore_async = boundary.pending;
702706

703707
boundary.update_pending_count(1);
704-
batch.increment();
708+
if (!ignore_async) batch.increment();
705709

706710
return function unsuspend() {
707711
boundary.update_pending_count(-1);
708712
batch.activate();
709-
batch.decrement();
713+
if (ignore_async) {
714+
// Template could have created new effects (for example via attachments) which need to be flushed
715+
Batch.enqueue(() => {
716+
batch.flush();
717+
});
718+
} else {
719+
batch.decrement();
720+
}
710721

711722
unset_context();
712723
};

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
/** @import { Derived, Effect, Source } from '#client' */
2-
/** @import { Batch } from './batch.js'; */
32
import { DEV } from 'esm-env';
43
import {
54
ERROR_VALUE,
@@ -33,7 +32,7 @@ import { tracing_mode_flag } from '../../flags/index.js';
3332
import { Boundary } from '../dom/blocks/boundary.js';
3433
import { component_context } from '../context.js';
3534
import { UNINITIALIZED } from '../../../constants.js';
36-
import { batch_deriveds, current_batch } from './batch.js';
35+
import { Batch, batch_deriveds, current_batch } from './batch.js';
3736
import { unset_context } from './async.js';
3837

3938
/** @type {Effect | null} */
@@ -135,10 +134,14 @@ export function async_derived(fn, location) {
135134
prev = promise;
136135

137136
var batch = /** @type {Batch} */ (current_batch);
137+
// In case the pending snippet is shown, we want to update the UI immediately
138+
// and not have the batch be blocked on async work,
139+
// since the async work is happening "hidden" behind the pending snippet.
140+
var ignore_async = boundary.pending;
138141

139142
if (should_suspend) {
140143
boundary.update_pending_count(1);
141-
batch.increment();
144+
if (!ignore_async) batch.increment();
142145
}
143146

144147
/**
@@ -180,7 +183,14 @@ export function async_derived(fn, location) {
180183

181184
if (should_suspend) {
182185
boundary.update_pending_count(-1);
183-
batch.decrement();
186+
if (ignore_async) {
187+
// Template could have created new effects (for example via attachments) which need to be flushed
188+
Batch.enqueue(() => {
189+
batch.flush();
190+
});
191+
} else {
192+
batch.decrement();
193+
}
184194
}
185195

186196
unset_context();
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { tick } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, target }) {
6+
const [increment, resolve] = target.querySelectorAll('button');
7+
8+
assert.htmlEqual(
9+
target.innerHTML,
10+
`
11+
<button>0</button>
12+
<button>shift</button>
13+
`
14+
);
15+
16+
increment.click();
17+
await tick();
18+
19+
assert.htmlEqual(
20+
target.innerHTML,
21+
`
22+
<button>1</button>
23+
<button>shift</button>
24+
`
25+
);
26+
27+
increment.click();
28+
await tick();
29+
30+
assert.htmlEqual(
31+
target.innerHTML,
32+
`
33+
<button>2</button>
34+
<button>shift</button>
35+
<p>loading...</p>
36+
`
37+
);
38+
39+
resolve.click();
40+
await tick();
41+
42+
assert.htmlEqual(
43+
target.innerHTML,
44+
`
45+
<button>2</button>
46+
<button>shift</button>
47+
<p>2</p>
48+
`
49+
);
50+
51+
increment.click();
52+
await tick();
53+
54+
assert.htmlEqual(
55+
target.innerHTML,
56+
`
57+
<button>2</button>
58+
<button>shift</button>
59+
<p>2</p>
60+
`
61+
);
62+
63+
resolve.click();
64+
await tick();
65+
66+
assert.htmlEqual(
67+
target.innerHTML,
68+
`
69+
<button>3</button>
70+
<button>shift</button>
71+
<p>3</p>
72+
`
73+
);
74+
}
75+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script>
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}>{count}</button>
14+
<button onclick={() => deferreds.shift()?.resolve(count)}>shift</button>
15+
16+
{#if count > 1}
17+
<svelte:boundary>
18+
<p>{await push(count)}</p>
19+
20+
{#snippet pending()}
21+
<p>loading...</p>
22+
{/snippet}
23+
</svelte:boundary>
24+
{/if}

0 commit comments

Comments
 (0)