From f7b194fb110cc6c6f0ec254481d00b7adad8ff8a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 22 Feb 2024 16:12:56 -0500 Subject: [PATCH] await blocks --- .../src/internal/client/dom/blocks/await.js | 258 ++++++------------ .../svelte/src/internal/client/runtime.js | 5 + 2 files changed, 84 insertions(+), 179 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js index ede2a759e9..fc49ae5cd0 100644 --- a/packages/svelte/src/internal/client/dom/blocks/await.js +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -1,200 +1,100 @@ import { is_promise } from '../../../common.js'; import { hydrate_block_anchor } from '../../hydration.js'; -import { remove } from '../../reconciler.js'; -import { - current_block, - destroy_signal, - execute_effect, - flushSync, - push_destroy_fn -} from '../../runtime.js'; -import { render_effect } from '../../reactivity/computations.js'; -import { trigger_transitions } from '../../transitions.js'; -import { AWAIT_BLOCK, UNINITIALIZED } from '../../constants.js'; - -/** @returns {import('../../types.js').AwaitBlock} */ -export function create_await_block() { - return { - // dom - d: null, - // effect - e: null, - // parent - p: /** @type {import('../../types.js').Block} */ (current_block), - // pending - n: true, - // transition - r: null, - // type - t: AWAIT_BLOCK - }; -} +import { set_current_effect } from '../../runtime.js'; +import { pause_effect, render_effect, resume_effect } from '../../reactivity/computations.js'; +import { BRANCH_EFFECT } from '../../constants.js'; /** * @template V * @param {Comment} anchor_node - * @param {(() => Promise)} input + * @param {(() => Promise)} get_input * @param {null | ((anchor: Node) => void)} pending_fn * @param {null | ((anchor: Node, value: V) => void)} then_fn * @param {null | ((anchor: Node, error: unknown) => void)} catch_fn * @returns {void} */ -export function await_block(anchor_node, input, pending_fn, then_fn, catch_fn) { - const block = create_await_block(); - - /** @type {null | import('../../types.js').Render} */ - let current_render = null; +export function await_block(anchor_node, get_input, pending_fn, then_fn, catch_fn) { hydrate_block_anchor(anchor_node); - /** @type {{}} */ - let latest_token; - - /** @type {typeof UNINITIALIZED | V} */ - let resolved_value = UNINITIALIZED; - - /** @type {unknown} */ - let error = UNINITIALIZED; - let pending = false; - block.r = - /** - * @param {import('../../types.js').Transition} transition - * @returns {void} - */ - (transition) => { - const render = /** @type {import('../../types.js').Render} */ (current_render); - const transitions = render.s; - transitions.add(transition); - transition.f(() => { - transitions.delete(transition); - if (transitions.size === 0) { - // If the current render has changed since, then we can remove the old render - // effect as it's stale. - if (current_render !== render && render.e !== null) { - if (render.d !== null) { - remove(render.d); - render.d = null; - } - destroy_signal(render.e); - render.e = null; + /** @type {any} */ + let input; + + /** @type {import('../../types.js').EffectSignal | null} */ + let pending_effect; + + /** @type {import('../../types.js').EffectSignal | null} */ + let then_effect; + + /** @type {import('../../types.js').EffectSignal | null} */ + let catch_effect; + + const branch = render_effect(() => { + if (input === (input = get_input())) return; + + if (is_promise(input)) { + const promise = /** @type {Promise} */ (input); + + if (pending_effect) { + resume_effect(pending_effect); + } else if (pending_fn) { + pending_effect = render_effect(() => pending_fn(anchor_node), {}, true); + } + + if (then_effect) { + pause_effect(then_effect, () => { + then_effect = null; + }); + } + + if (catch_effect) { + pause_effect(catch_effect, () => { + catch_effect = null; + }); + } + + promise + .then((value) => { + if (promise !== input) return; + + if (pending_effect) { + pause_effect(pending_effect, () => { + pending_effect = null; + }); } - } - }); - }; - const create_render_effect = () => { - /** @type {import('../../types.js').Render} */ - const render = { - d: null, - e: null, - s: new Set(), - p: current_render - }; - const effect = render_effect( - () => { - if (error === UNINITIALIZED) { - if (resolved_value === UNINITIALIZED) { - // pending = true - block.n = true; - if (pending_fn !== null) { - pending_fn(anchor_node); + + if (then_fn) { + if (then_effect) { + resume_effect(then_effect); + } else if (pending_fn) { + set_current_effect(branch); + then_effect = render_effect(() => then_fn(anchor_node, value), {}, true); + set_current_effect(null); } - } else if (then_fn !== null) { - // pending = false - block.n = false; - then_fn(anchor_node, resolved_value); } - } else if (catch_fn !== null) { - // pending = false - block.n = false; - catch_fn(anchor_node, error); - } - render.d = block.d; - block.d = null; - }, - block, - true, - true - ); - render.e = effect; - current_render = render; - }; - const render = () => { - const render = current_render; - if (render === null) { - create_render_effect(); - return; - } - const transitions = render.s; - if (transitions.size === 0) { - if (render.d !== null) { - remove(render.d); - render.d = null; - } - if (render.e) { - execute_effect(render.e); - } else { - create_render_effect(); - } - } else { - create_render_effect(); - trigger_transitions(transitions, 'out'); - } - }; - const await_effect = render_effect( - () => { - const token = {}; - latest_token = token; - const promise = input(); - if (is_promise(promise)) { - promise.then( - /** @param {V} v */ - (v) => { - if (latest_token === token) { - // Ensure UI is in sync before resolving value. - flushSync(); - resolved_value = v; - pending = false; - render(); + }) + .catch((error) => { + if (promise !== input) return; + + if (pending_effect) { + pause_effect(pending_effect, () => { + pending_effect = null; + }); + } + + if (catch_fn) { + if (catch_effect) { + resume_effect(catch_effect); + } else if (pending_fn) { + set_current_effect(branch); + catch_effect = render_effect(() => catch_fn(anchor_node, error), {}, true); + set_current_effect(null); } - }, - /** @param {unknown} _error */ - (_error) => { - error = _error; - pending = false; - render(); } - ); - if (resolved_value !== UNINITIALIZED || error !== UNINITIALIZED) { - error = UNINITIALIZED; - resolved_value = UNINITIALIZED; - } - if (!pending) { - pending = true; - render(); - } - } else { - error = UNINITIALIZED; - resolved_value = promise; - pending = false; - render(); - } - }, - block, - false - ); - push_destroy_fn(await_effect, () => { - let render = current_render; - latest_token = {}; - while (render !== null) { - const dom = render.d; - if (dom !== null) { - remove(dom); - } - const effect = render.e; - if (effect !== null) { - destroy_signal(effect); - } - render = render.p; + }); + } else { + // TODO handle non-promises } }); - block.e = await_effect; + + branch.f |= BRANCH_EFFECT; // TODO create a primitive for this } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index c67301a598..5b072fd7e6 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -63,6 +63,11 @@ export let current_consumer = null; /** @type {null | import('./types.js').EffectSignal} */ export let current_effect = null; +/** @param {null | import('./types.js').EffectSignal} effect */ +export function set_current_effect(effect) { + current_effect = effect; +} + /** @type {null | import('./types.js').Signal[]} */ let current_dependencies = null; let current_dependencies_index = 0;