Add async functions utils.
This commit is contained in:
		
							parent
							
								
									334a311c9e
								
							
						
					
					
						commit
						20c36f7c8b
					
				
					 1 changed files with 111 additions and 0 deletions
				
			
		
							
								
								
									
										111
									
								
								src/Async.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/Async.tsx
									
										
									
									
									
										Normal 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);
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		
		Reference in a new issue