rvx
Version:
A signal based rendering library
95 lines (86 loc) • 2.99 kB
text/typescript
import { isolate } from "../core/isolate.js";
import { $ } from "../core/signals.js";
import type { Component } from "../core/types.js";
import { nest, View } from "../core/view.js";
import { ASYNC } from "./async-context.js";
/**
* Render content depending on the state of an async function or promise.
*
* See {@link Async `<Async>`} when using JSX or when named properties are preferred.
*
* This task is tracked using the current {@link ASYNC async context} if any. It is guaranteed, that the view is updated before the tracked task completes.
*
* @param source The async function or promise.
* + If this is a function, it runs {@link isolate isolated}.
* @param component A component to render content when resolved.
* + The resolved value is passed as the first argument.
* + Nothing is rendered by default.
* @param pending A component to render while pending.
* + Nothing is rendered by default.
* @param rejected A component to render content when rejected.
* + The rejected error is passed as the first argument.
* + Nothing is rendered by default.
*/
export function nestAsync<T>(source: (() => Promise<T>) | Promise<T>, component?: Component<T>, pending?: Component, rejected?: Component<unknown>): View {
const state = $({ type: 0, value: undefined as unknown });
let promise: Promise<T>;
if (typeof source === "function") {
promise = isolate(source);
} else {
promise = source;
}
const ac = ASYNC.current;
promise.then(value => {
state.value = { type: 1, value };
}, (value: unknown) => {
state.value = { type: 2, value };
if (ac === undefined && rejected === undefined) {
void Promise.reject(value);
}
});
ac?.track(promise);
return nest(state, state => {
switch (state.type) {
case 0: return pending?.();
case 1: return component?.(state.value as T);
case 2: return rejected?.(state.value);
}
});
}
/**
* Render content depending on the state of an async function or promise.
*
* See {@link nestAsync} when not using JSX or when positional arguments are preferred.
*
* This task is tracked using the current {@link ASYNC async context} if any. It is guaranteed, that the view is updated before the tracked task completes.
*/
export function Async<T>(props: {
/**
* The async function or promise.
*
* If this is a function, it runs {@link isolate isolated}.
*/
source: (() => Promise<T>) | Promise<T>;
/**
* A component to render content when resolved.
*
* + The resolved value is passed as the first argument.
* + Nothing is rendered by default.
*/
children?: Component<T>;
/**
* A component render content while pending.
*
* + Nothing is rendered by default.
*/
pending?: Component;
/**
* A component to render content when rejected.
*
* + The rejected error is passed as the first argument.
* + Nothing is rendered by default.
*/
rejected?: Component<unknown>;
}): View {
return nestAsync(props.source, props.children, props.pending, props.rejected);
}