diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 9babcaa073..3747840f0f 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -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,55 +100,65 @@ export function async_derived(fn, detect_waterfall = true) { } var promise = /** @type {Promise} */ (/** @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; + if (should_suspend) unsuspend ??= suspend(); - try { - var v = await promise; + promise.then( + (v) => { + if ((parent.f & DESTROYED) !== 0) { + return; + } - if ((parent.f & DESTROYED) !== 0) { - return; - } + if (promise === current) { + restore(); + from_async_derived = null; - if (promise === current) { - restore(); - from_async_derived = null; + internal_set(signal, v); - internal_set(value, v); + if (DEV && detect_waterfall) { + recent_async_deriveds.add(signal); - if (DEV && detect_waterfall) { - recent_async_deriveds.add(value); + setTimeout(() => { + if (recent_async_deriveds.has(signal)) { + w.await_waterfall(); + recent_async_deriveds.delete(signal); + } + }); + } - setTimeout(() => { - if (recent_async_deriveds.has(value)) { - w.await_waterfall(); - recent_async_deriveds.delete(value); - } - }); + // TODO we should probably null out active effect here, + // rather than inside `restore()` + unsuspend?.(); + 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); - 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); + }); } /** diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-invalidation-during-init/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-derived-invalidation-during-init/Child.svelte new file mode 100644 index 0000000000..ffcd8b46b4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-invalidation-during-init/Child.svelte @@ -0,0 +1,9 @@ + + +

{(await d).value}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-invalidation-during-init/_config.js b/packages/svelte/tests/runtime-runes/samples/async-derived-invalidation-during-init/_config.js new file mode 100644 index 0000000000..c8f20d9597 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-invalidation-during-init/_config.js @@ -0,0 +1,43 @@ +import { flushSync, tick } from 'svelte'; +import { deferred } from '../../../../src/internal/shared/utils.js'; +import { test } from '../../test'; + +/** @type {ReturnType} */ +let d1; + +export default test({ + html: `

pending

`, + + 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, '

hello

'); + + assert.deepEqual(errors, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-invalidation-during-init/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-derived-invalidation-during-init/main.svelte new file mode 100644 index 0000000000..718a256b86 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-invalidation-during-init/main.svelte @@ -0,0 +1,13 @@ + + + + + + {#snippet pending()} +

pending

+ {/snippet} +