await blocks

blockless
Rich Harris 10 months ago
parent cffc51d007
commit f7b194fb11

@ -1,200 +1,100 @@
import { is_promise } from '../../../common.js'; import { is_promise } from '../../../common.js';
import { hydrate_block_anchor } from '../../hydration.js'; import { hydrate_block_anchor } from '../../hydration.js';
import { remove } from '../../reconciler.js'; import { set_current_effect } from '../../runtime.js';
import { import { pause_effect, render_effect, resume_effect } from '../../reactivity/computations.js';
current_block, import { BRANCH_EFFECT } from '../../constants.js';
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
};
}
/** /**
* @template V * @template V
* @param {Comment} anchor_node * @param {Comment} anchor_node
* @param {(() => Promise<V>)} input * @param {(() => Promise<V>)} get_input
* @param {null | ((anchor: Node) => void)} pending_fn * @param {null | ((anchor: Node) => void)} pending_fn
* @param {null | ((anchor: Node, value: V) => void)} then_fn * @param {null | ((anchor: Node, value: V) => void)} then_fn
* @param {null | ((anchor: Node, error: unknown) => void)} catch_fn * @param {null | ((anchor: Node, error: unknown) => void)} catch_fn
* @returns {void} * @returns {void}
*/ */
export function await_block(anchor_node, input, pending_fn, then_fn, catch_fn) { export function await_block(anchor_node, get_input, pending_fn, then_fn, catch_fn) {
const block = create_await_block();
/** @type {null | import('../../types.js').Render} */
let current_render = null;
hydrate_block_anchor(anchor_node); hydrate_block_anchor(anchor_node);
/** @type {{}} */ /** @type {any} */
let latest_token; let input;
/** @type {typeof UNINITIALIZED | V} */ /** @type {import('../../types.js').EffectSignal | null} */
let resolved_value = UNINITIALIZED; let pending_effect;
/** @type {unknown} */ /** @type {import('../../types.js').EffectSignal | null} */
let error = UNINITIALIZED; let then_effect;
let pending = false;
block.r = /** @type {import('../../types.js').EffectSignal | null} */
/** let catch_effect;
* @param {import('../../types.js').Transition} transition
* @returns {void} const branch = render_effect(() => {
*/ if (input === (input = get_input())) return;
(transition) => {
const render = /** @type {import('../../types.js').Render} */ (current_render); if (is_promise(input)) {
const transitions = render.s; const promise = /** @type {Promise<any>} */ (input);
transitions.add(transition);
transition.f(() => { if (pending_effect) {
transitions.delete(transition); resume_effect(pending_effect);
if (transitions.size === 0) { } else if (pending_fn) {
// If the current render has changed since, then we can remove the old render pending_effect = render_effect(() => pending_fn(anchor_node), {}, true);
// effect as it's stale. }
if (current_render !== render && render.e !== null) {
if (render.d !== null) { if (then_effect) {
remove(render.d); pause_effect(then_effect, () => {
render.d = null; then_effect = null;
} });
destroy_signal(render.e); }
render.e = 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;
});
} }
}
}); if (then_fn) {
}; if (then_effect) {
const create_render_effect = () => { resume_effect(then_effect);
/** @type {import('../../types.js').Render} */ } else if (pending_fn) {
const render = { set_current_effect(branch);
d: null, then_effect = render_effect(() => then_fn(anchor_node, value), {}, true);
e: null, set_current_effect(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);
} }
} else if (then_fn !== null) {
// pending = false
block.n = false;
then_fn(anchor_node, resolved_value);
} }
} else if (catch_fn !== null) { })
// pending = false .catch((error) => {
block.n = false; if (promise !== input) return;
catch_fn(anchor_node, error);
} if (pending_effect) {
render.d = block.d; pause_effect(pending_effect, () => {
block.d = null; pending_effect = null;
}, });
block, }
true,
true if (catch_fn) {
); if (catch_effect) {
render.e = effect; resume_effect(catch_effect);
current_render = render; } else if (pending_fn) {
}; set_current_effect(branch);
const render = () => { catch_effect = render_effect(() => catch_fn(anchor_node, error), {}, true);
const render = current_render; set_current_effect(null);
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();
} }
},
/** @param {unknown} _error */
(_error) => {
error = _error;
pending = false;
render();
} }
); });
if (resolved_value !== UNINITIALIZED || error !== UNINITIALIZED) { } else {
error = UNINITIALIZED; // TODO handle non-promises
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;
} }
}); });
block.e = await_effect;
branch.f |= BRANCH_EFFECT; // TODO create a primitive for this
} }

@ -63,6 +63,11 @@ export let current_consumer = null;
/** @type {null | import('./types.js').EffectSignal} */ /** @type {null | import('./types.js').EffectSignal} */
export let current_effect = null; 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[]} */ /** @type {null | import('./types.js').Signal[]} */
let current_dependencies = null; let current_dependencies = null;
let current_dependencies_index = 0; let current_dependencies_index = 0;

Loading…
Cancel
Save