mirror of
https://github.com/robonen/tools.git
synced 2026-03-20 19:04:46 +00:00
feat(web/vue): enhance async state management for useAsyncState with improved error handling and loading states
This commit is contained in:
@@ -1,13 +1,8 @@
|
||||
import { ref, shallowRef } from 'vue';
|
||||
import { isFunction } from '@robonen/stdlib';
|
||||
|
||||
export enum AsyncStateStatus {
|
||||
PENDING,
|
||||
FULFILLED,
|
||||
REJECTED,
|
||||
}
|
||||
import { ref, shallowRef, watch, type Ref, type ShallowRef, type UnwrapRef } from 'vue';
|
||||
import { isFunction, sleep } from '@robonen/stdlib';
|
||||
|
||||
export interface UseAsyncStateOptions<Shallow extends boolean, Data = any> {
|
||||
delay?: number;
|
||||
shallow?: Shallow;
|
||||
immediate?: boolean;
|
||||
resetOnExecute?: boolean;
|
||||
@@ -16,6 +11,19 @@ export interface UseAsyncStateOptions<Shallow extends boolean, Data = any> {
|
||||
onSuccess?: (data: Data) => void;
|
||||
}
|
||||
|
||||
export interface UseAsyncStateReturnBase<Data, Params extends any[], Shallow extends boolean> {
|
||||
state: Shallow extends true ? ShallowRef<Data> : Ref<UnwrapRef<Data>>;
|
||||
isLoading: Ref<boolean>;
|
||||
isReady: Ref<boolean>;
|
||||
error: Ref<unknown | null>;
|
||||
execute: (delay: number, ...params: Params) => Promise<Data>;
|
||||
executeImmediately: (...params: Params) => Promise<Data>;
|
||||
}
|
||||
|
||||
export type UseAsyncStateReturn<Data, Params extends any[], Shallow extends boolean> =
|
||||
& UseAsyncStateReturnBase<Data, Params, Shallow>
|
||||
& PromiseLike<UseAsyncStateReturnBase<Data, Params, Shallow>>;
|
||||
|
||||
/**
|
||||
* @name useAsyncState
|
||||
* @category State
|
||||
@@ -25,35 +33,82 @@ export function useAsyncState<Data, Params extends any[] = [], Shallow extends b
|
||||
maybePromise: Promise<Data> | ((...args: Params) => Promise<Data>),
|
||||
initialState: Data,
|
||||
options?: UseAsyncStateOptions<Shallow, Data>,
|
||||
) {
|
||||
): UseAsyncStateReturn<Data, Params, Shallow> {
|
||||
const state = options?.shallow ? shallowRef(initialState) : ref(initialState);
|
||||
const status = ref<AsyncStateStatus | null>(null);
|
||||
const error = ref<unknown | null>(null);
|
||||
const isLoading = ref(false);
|
||||
const isReady = ref(false);
|
||||
|
||||
const execute = async (...params: any[]) => {
|
||||
const execute = async (delay = options?.delay ?? 0, ...params: any[]) => {
|
||||
if (options?.resetOnExecute)
|
||||
state.value = initialState;
|
||||
|
||||
status.value = AsyncStateStatus.PENDING;
|
||||
isLoading.value = true;
|
||||
isReady.value = false;
|
||||
error.value = null;
|
||||
|
||||
if (delay > 0)
|
||||
await sleep(delay);
|
||||
|
||||
const promise = isFunction(maybePromise) ? maybePromise(...params as Params) : maybePromise;
|
||||
|
||||
try {
|
||||
const data = await promise;
|
||||
state.value = data;
|
||||
status.value = AsyncStateStatus.FULFILLED;
|
||||
isReady.value = true;
|
||||
options?.onSuccess?.(data);
|
||||
}
|
||||
catch (error) {
|
||||
status.value = AsyncStateStatus.REJECTED;
|
||||
options?.onError?.(error);
|
||||
catch (e: unknown) {
|
||||
error.value = e;
|
||||
options?.onError?.(e);
|
||||
|
||||
if (options?.throwError)
|
||||
throw error;
|
||||
}
|
||||
finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
return state.value as Data;
|
||||
};
|
||||
|
||||
const executeImmediately = (...params: Params) => {
|
||||
return execute(0, ...params);
|
||||
};
|
||||
|
||||
if (options?.immediate)
|
||||
execute();
|
||||
|
||||
const shell = {
|
||||
state: state as Shallow extends true ? ShallowRef<Data> : Ref<UnwrapRef<Data>>,
|
||||
isLoading,
|
||||
isReady,
|
||||
error,
|
||||
execute,
|
||||
executeImmediately,
|
||||
};
|
||||
|
||||
function waitResolve() {
|
||||
return new Promise<UseAsyncStateReturnBase<Data, Params, Shallow>>((resolve, reject) => {
|
||||
watch(
|
||||
isLoading,
|
||||
(loading) => {
|
||||
if (loading === false)
|
||||
error.value ? reject(error.value) : resolve(shell);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
once: true,
|
||||
flush: 'sync',
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...shell,
|
||||
then(onFulfilled, onRejected) {
|
||||
return waitResolve().then(onFulfilled, onRejected);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user