Add async functions utils.

This commit is contained in:
Madeorsk 2024-07-13 17:34:23 +02:00
parent 334a311c9e
commit 20c36f7c8b
Signed by: Madeorsk
SSH key fingerprint: SHA256:J9G0ofIOLKf7kyS2IfrMqtMaPdfsk1W02+oGueZzDDU

111
src/Async.tsx Normal file
View file

@ -0,0 +1,111 @@
import React, {useMemo, useState} from "react";
/**
* Asynchronous data state.
*/
interface AsyncState<T>
{
/**
* Determine if we are waiting for the promise result or not.
*/
pending: boolean;
/**
* The promise which is retrieved (or has retrieved) data.
*/
promise: Promise<T>;
/**
* Error thrown by the promise.
*/
error: Error;
/**
* The promise result.
*/
data: T;
}
/**
* A promise production function.
*/
export type PromiseFn<T> = () => Promise<T>;
/**
* React hook for promise result retrieval.
* @param promise The promise or a function that produces a promise.
* @param deps When one of the `deps` change, it will wait for the promise again.
*/
export function useAsync<T>(promise: Promise<T>|PromiseFn<T>, deps: any[] = []): AsyncState<T>
{
// Get the actual promise from the function if there is one.
if ((promise as PromiseFn<T>)?.call)
promise = (promise as PromiseFn<T>)();
else
promise = Promise.race([promise as Promise<T>]);
// The async state.
const [state, setState] = useState<AsyncState<T>>({
pending: true,
promise: promise,
error: undefined,
data: undefined,
});
/**
* Partial update of an async state.
* @param stateUpdate A partial update object.
*/
const updateState = (stateUpdate: Partial<AsyncState<T>>) => {
// Copy the original state and apply the partial state update.
setState(Object.assign({}, state, stateUpdate));
};
// Reconfigure the promise when any deps have changed.
useMemo(() => {
(promise as Promise<T>).then((result) => {
// When there is a result, disable pending state and set retrieved data, without error.
updateState({
pending: false,
error: undefined,
data: result,
})
}).catch((error) => {
// An error happened, disable pending state, reset data, and set the error.
updateState({
pending: false,
error: error,
data: undefined,
})
});
// Promise is ready: reset the state to pending with the configured promise, without data and error.
updateState({
pending: true,
promise: promise as Promise<T>,
error: undefined,
data: undefined,
});
}, deps);
return state; // Return the current async state.
}
/**
* Wait for the promise to be fulfilled to render the children.
* @param async The async state.
* @param children Renderer function of the children, takes promised data as argument.
* @param fallback Content shown when the promise is not fulfilled yet.
* @constructor
*/
export function Await<T>({ async, children, fallback }: {
async: AsyncState<T>;
children: (async: T) => React.ReactElement;
fallback?: React.ReactElement;
})
{
// Still waiting for the promised data, showing fallback content.
if (async.pending) return fallback ?? <></>;
// An error happened, throwing it.
if (async.error) throw async.error;
// Promise is fulfilled, rendering the children with result data.
return children(async.data);
}