pull/10798/head
Rich Harris 2 years ago
parent 2fb3cfcb13
commit e64f9afc2c

@ -1,10 +1,13 @@
import { is_promise } from '../../../common.js';
import { hydrate_block_anchor } from '../hydration.js';
import { remove } from '../reconciler.js';
import { current_block, execute_effect, flushSync } from '../../runtime.js';
import { destroy_effect, render_effect } from '../../reactivity/effects.js';
import { trigger_transitions } from '../elements/transitions.js';
import { UNINITIALIZED } from '../../constants.js';
import {
current_block,
current_component_context,
set_current_component_context,
set_current_effect
} from '../../runtime.js';
import { pause_effect, render_effect, resume_effect } from '../../reactivity/effects.js';
/** @returns {import('../../types.js').AwaitBlock} */
export function create_await_block() {
@ -23,144 +26,181 @@ export function create_await_block() {
/**
* @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) {
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;
const component_context = current_component_context;
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;
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);
}
} 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;
/** @type {any} */
let input;
/** @type {import('#client').Effect | null} */
let pending_effect;
/** @type {import('#client').Effect | null} */
let then_effect;
/** @type {import('#client').Effect | null} */
let catch_effect;
// TODO tidy this up
/** @type {{ d?: any } | null} */
let pending_block;
/** @type {{ d?: any } | null} */
let then_block;
/** @type {{ d?: any } | null} */
let catch_block;
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), (pending_block = {}), true);
}
if (render.e) {
execute_effect(render.e);
} else {
create_render_effect();
if (then_effect) {
pause_effect(then_effect, () => {
// TODO make this unnecessary
const dom = then_block?.d;
if (dom) remove(dom);
then_effect = then_block = null;
});
}
} 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();
if (catch_effect) {
pause_effect(catch_effect, () => {
// TODO make this unnecessary
const dom = catch_block?.d;
if (dom) remove(dom);
catch_effect = catch_block = null;
});
}
promise
.then((value) => {
if (promise !== input) return;
if (pending_effect) {
pause_effect(pending_effect, () => {
// TODO make this unnecessary
const dom = pending_block?.d;
if (dom) remove(dom);
pending_effect = pending_block = null;
});
}
if (then_fn) {
if (then_effect) {
resume_effect(then_effect);
} else if (then_fn) {
set_current_effect(branch);
set_current_component_context(component_context);
then_effect = render_effect(
() => then_fn(anchor_node, value),
(then_block = {}),
true
);
set_current_component_context(null);
set_current_effect(null);
}
},
/** @param {unknown} _error */
(_error) => {
error = _error;
pending = false;
render();
}
);
if (resolved_value !== UNINITIALIZED || error !== UNINITIALIZED) {
error = UNINITIALIZED;
resolved_value = UNINITIALIZED;
})
.catch(() => {});
promise.catch((error) => {
if (promise !== input) return;
if (pending_effect) {
pause_effect(pending_effect, () => {
// TODO make this unnecessary
const dom = pending_block?.d;
if (dom) remove(dom);
pending_effect = pending_block = null;
});
}
if (!pending) {
pending = true;
render();
if (catch_fn) {
if (catch_effect) {
resume_effect(catch_effect);
} else if (catch_fn) {
set_current_effect(branch);
set_current_component_context(component_context);
catch_effect = render_effect(
() => catch_fn(anchor_node, error),
(catch_block = {}),
true
);
set_current_component_context(null);
set_current_effect(null);
}
}
} else {
error = UNINITIALIZED;
resolved_value = promise;
pending = false;
render();
});
} else {
if (pending_effect) {
pause_effect(pending_effect, () => {
// TODO make this unnecessary
const dom = pending_block?.d;
if (dom) remove(dom);
pending_effect = pending_block = null;
});
}
},
block,
false
);
await_effect.ondestroy = () => {
let render = current_render;
latest_token = {};
while (render !== null) {
const dom = render.d;
if (dom !== null) {
remove(dom);
if (then_effect) {
resume_effect(then_effect);
} else if (then_fn) {
// TODO we need to pass a function in rather than a value, because
// this will never update
then_effect = render_effect(() => then_fn(anchor_node, input), (then_block = {}), true);
}
const effect = render.e;
if (effect !== null) {
destroy_effect(effect);
if (catch_effect) {
pause_effect(catch_effect, () => {
// TODO make this unnecessary
const dom = catch_block?.d;
if (dom) remove(dom);
catch_effect = catch_block = null;
});
}
render = render.p;
}
}, block);
branch.ondestroy = () => {
if (block.d) {
remove(block.d);
}
// TODO this sucks, tidy it up
if (pending_block?.d) {
remove(pending_block.d);
}
if (then_block?.d) {
remove(then_block.d);
}
if (catch_block?.d) {
remove(catch_block.d);
}
};
block.e = await_effect;
}

@ -57,6 +57,11 @@ export let current_reaction = null;
/** @type {null | import('./types.js').Effect} */
export let current_effect = null;
/** @param {null | import('./types.js').Effect} effect */
export function set_current_effect(effect) {
current_effect = effect;
}
/** @type {null | import('./types.js').Value[]} */
export let current_dependencies = null;
let current_dependencies_index = 0;
@ -104,6 +109,11 @@ export let current_block = null;
/** @type {import('./types.js').ComponentContext | null} */
export let current_component_context = null;
/** @param {import('./types.js').ComponentContext | null} context */
export function set_current_component_context(context) {
current_component_context = context;
}
/** @returns {boolean} */
export function is_runes() {
return current_component_context !== null && current_component_context.r;

Loading…
Cancel
Save