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 { capture, suspend } from '../dom/blocks/boundary.js';
import { component_context } from '../context.js'; import { component_context } from '../context.js';
import { noop } from '../../shared/utils.js'; import { noop } from '../../shared/utils.js';
import { UNINITIALIZED } from '../../../constants.js';
/** @type {Effect | null} */ /** @type {Effect | null} */
export let from_async_derived = null; export let from_async_derived = null;
@ -99,55 +100,65 @@ export function async_derived(fn, detect_waterfall = true) {
} }
var promise = /** @type {Promise<V>} */ (/** @type {unknown} */ (undefined)); 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 // only suspend in async deriveds created on initialisation
var should_suspend = !active_reaction; var should_suspend = !active_reaction;
/** @type {(() => void) | null} */
var unsuspend = null;
// TODO this isn't a block // TODO this isn't a block
block(async () => { block(() => {
if (DEV) from_async_derived = active_effect; if (DEV) from_async_derived = active_effect;
var current = (promise = fn()); var current = (promise = fn());
if (DEV) from_async_derived = null; if (DEV) from_async_derived = null;
var restore = capture(); var restore = capture();
var unsuspend = should_suspend ? suspend() : noop; if (should_suspend) unsuspend ??= suspend();
try { promise.then(
var v = await promise; (v) => {
if ((parent.f & DESTROYED) !== 0) {
return;
}
if ((parent.f & DESTROYED) !== 0) { if (promise === current) {
return; restore();
} from_async_derived = null;
if (promise === current) { internal_set(signal, v);
restore();
from_async_derived = null;
internal_set(value, v); if (DEV && detect_waterfall) {
recent_async_deriveds.add(signal);
if (DEV && detect_waterfall) { setTimeout(() => {
recent_async_deriveds.add(value); if (recent_async_deriveds.has(signal)) {
w.await_waterfall();
recent_async_deriveds.delete(signal);
}
});
}
setTimeout(() => { // TODO we should probably null out active effect here,
if (recent_async_deriveds.has(value)) { // rather than inside `restore()`
w.await_waterfall(); unsuspend?.();
recent_async_deriveds.delete(value); unsuspend = null;
}
});
} }
},
(e) => {
handle_error(e, parent, null, parent.ctx);
} }
} catch (e) { );
handle_error(e, parent, null, parent.ctx);
} finally {
unsuspend();
// TODO we should probably null out active effect here,
// rather than inside `restore()`
}
}, EFFECT_PRESERVED); }, 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