await blocks

blockless
Rich Harris 10 months ago
parent cffc51d007
commit f7b194fb11

@ -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<V>)} input
* @param {(() => Promise<V>)} 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<any>} */ (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
}

@ -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;

Loading…
Cancel
Save