delay initial async derived resolution if invalidated during init

async-abort-signal
Rich Harris 7 months ago
parent 2703ac6096
commit 726b3f1b85

@ -30,6 +30,7 @@ import { tracing_mode_flag } from '../../flags/index.js';
import { capture, suspend } from '../dom/blocks/boundary.js';
import { component_context } from '../context.js';
import { noop } from '../../shared/utils.js';
import { UNINITIALIZED } from '../../../constants.js';
/** @type {Effect | null} */
export let from_async_derived = null;
@ -99,23 +100,25 @@ export function async_derived(fn, detect_waterfall = true) {
}
var promise = /** @type {Promise<V>} */ (/** @type {unknown} */ (undefined));
var value = source(/** @type {V} */ (undefined));
var signal = source(/** @type {V} */ (UNINITIALIZED));
// only suspend in async deriveds created on initialisation
var should_suspend = !active_reaction;
/** @type {(() => void) | null} */
var unsuspend = null;
// TODO this isn't a block
block(async () => {
block(() => {
if (DEV) from_async_derived = active_effect;
var current = (promise = fn());
if (DEV) from_async_derived = null;
var restore = capture();
var unsuspend = should_suspend ? suspend() : noop;
try {
var v = await promise;
if (should_suspend) unsuspend ??= suspend();
promise.then(
(v) => {
if ((parent.f & DESTROYED) !== 0) {
return;
}
@ -124,30 +127,38 @@ export function async_derived(fn, detect_waterfall = true) {
restore();
from_async_derived = null;
internal_set(value, v);
internal_set(signal, v);
if (DEV && detect_waterfall) {
recent_async_deriveds.add(value);
recent_async_deriveds.add(signal);
setTimeout(() => {
if (recent_async_deriveds.has(value)) {
if (recent_async_deriveds.has(signal)) {
w.await_waterfall();
recent_async_deriveds.delete(value);
recent_async_deriveds.delete(signal);
}
});
}
}
} catch (e) {
handle_error(e, parent, null, parent.ctx);
} finally {
unsuspend();
// TODO we should probably null out active effect here,
// rather than inside `restore()`
unsuspend?.();
unsuspend = null;
}
},
(e) => {
handle_error(e, parent, null, parent.ctx);
}
);
}, EFFECT_PRESERVED);
return Promise.resolve(promise).then(() => value);
return new Promise(async (fulfil) => {
// if the effect re-runs before the initial promise
// resolves, delay resolution until we have a value
var p;
while (p !== (p = promise)) await p;
fulfil(signal);
});
}
/**

@ -0,0 +1,9 @@
<script>
let { promise } = $props();
let d = $derived({
value: await promise
});
</script>
<p>{(await d).value}</p>

@ -0,0 +1,43 @@
import { flushSync, tick } from 'svelte';
import { deferred } from '../../../../src/internal/shared/utils.js';
import { test } from '../../test';
/** @type {ReturnType<typeof deferred>} */
let d1;
export default test({
html: `<p>pending</p>`,
get props() {
d1 = deferred();
return {
promise: d1.promise
};
},
async test({ assert, target, component, errors }) {
await Promise.resolve();
var d2 = deferred();
component.promise = d2.promise;
d1.resolve('unused');
await Promise.resolve();
await Promise.resolve();
d2.resolve('hello');
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
await tick();
assert.htmlEqual(target.innerHTML, '<p>hello</p>');
assert.deepEqual(errors, []);
}
});

@ -0,0 +1,13 @@
<script>
import Child from './Child.svelte';
let { promise } = $props();
</script>
<svelte:boundary>
<Child {promise} />
{#snippet pending()}
<p>pending</p>
{/snippet}
</svelte:boundary>
Loading…
Cancel
Save