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