ensure immediate flushing on pending boundaries

boundary-batch-first-run
Simon Holthausen 2 weeks ago
parent f8747b9774
commit 7c194acc98

@ -699,14 +699,25 @@ export function schedule_effect(signal) {
export function suspend() {
var boundary = get_pending_boundary();
var batch = /** @type {Batch} */ (current_batch);
// In case the pending snippet is shown, we want to update the UI immediately
// and not have the batch be blocked on async work,
// since the async work is happening "hidden" behind the pending snippet.
var ignore_async = boundary.pending;
boundary.update_pending_count(1);
batch.increment();
if (!ignore_async) batch.increment();
return function unsuspend() {
boundary.update_pending_count(-1);
batch.activate();
if (ignore_async) {
// Template could have created new effects (for example via attachments) which need to be flushed
Batch.enqueue(() => {
batch.flush();
});
} else {
batch.decrement();
}
unset_context();
};

@ -1,5 +1,4 @@
/** @import { Derived, Effect, Source } from '#client' */
/** @import { Batch } from './batch.js'; */
import { DEV } from 'esm-env';
import {
ERROR_VALUE,
@ -33,7 +32,7 @@ import { tracing_mode_flag } from '../../flags/index.js';
import { Boundary } from '../dom/blocks/boundary.js';
import { component_context } from '../context.js';
import { UNINITIALIZED } from '../../../constants.js';
import { batch_deriveds, current_batch } from './batch.js';
import { Batch, batch_deriveds, current_batch } from './batch.js';
import { unset_context } from './async.js';
/** @type {Effect | null} */
@ -135,10 +134,14 @@ export function async_derived(fn, location) {
prev = promise;
var batch = /** @type {Batch} */ (current_batch);
// In case the pending snippet is shown, we want to update the UI immediately
// and not have the batch be blocked on async work,
// since the async work is happening "hidden" behind the pending snippet.
var ignore_async = boundary.pending;
if (should_suspend) {
boundary.update_pending_count(1);
batch.increment();
if (!ignore_async) batch.increment();
}
/**
@ -180,8 +183,15 @@ export function async_derived(fn, location) {
if (should_suspend) {
boundary.update_pending_count(-1);
if (ignore_async) {
// Template could have created new effects (for example via attachments) which need to be flushed
Batch.enqueue(() => {
batch.flush();
});
} else {
batch.decrement();
}
}
unset_context();
};

@ -0,0 +1,75 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target }) {
const [increment, resolve] = target.querySelectorAll('button');
assert.htmlEqual(
target.innerHTML,
`
<button>0</button>
<button>shift</button>
`
);
increment.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>1</button>
<button>shift</button>
`
);
increment.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>2</button>
<button>shift</button>
<p>loading...</p>
`
);
resolve.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>2</button>
<button>shift</button>
<p>2</p>
`
);
increment.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>2</button>
<button>shift</button>
<p>2</p>
`
);
resolve.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>3</button>
<button>shift</button>
<p>3</p>
`
);
}
});

@ -0,0 +1,24 @@
<script>
let count = $state(0);
let deferreds = [];
function push() {
const deferred = Promise.withResolvers();
deferreds.push(deferred);
return deferred.promise;
}
</script>
<button onclick={() => count += 1}>{count}</button>
<button onclick={() => deferreds.shift()?.resolve(count)}>shift</button>
{#if count > 1}
<svelte:boundary>
<p>{await push(count)}</p>
{#snippet pending()}
<p>loading...</p>
{/snippet}
</svelte:boundary>
{/if}
Loading…
Cancel
Save