1
0
mirror of https://github.com/robonen/tools.git synced 2026-03-20 02:44:45 +00:00

feat(web/vue): enhance async state management for useAsyncState with improved error handling and loading states

This commit is contained in:
2025-07-10 05:34:12 +07:00
parent aa8a0f00f3
commit 1e9859da83

View File

@@ -1,13 +1,8 @@
import { ref, shallowRef } from 'vue'; import { ref, shallowRef, watch, type Ref, type ShallowRef, type UnwrapRef } from 'vue';
import { isFunction } from '@robonen/stdlib'; import { isFunction, sleep } from '@robonen/stdlib';
export enum AsyncStateStatus {
PENDING,
FULFILLED,
REJECTED,
}
export interface UseAsyncStateOptions<Shallow extends boolean, Data = any> { export interface UseAsyncStateOptions<Shallow extends boolean, Data = any> {
delay?: number;
shallow?: Shallow; shallow?: Shallow;
immediate?: boolean; immediate?: boolean;
resetOnExecute?: boolean; resetOnExecute?: boolean;
@@ -16,6 +11,19 @@ export interface UseAsyncStateOptions<Shallow extends boolean, Data = any> {
onSuccess?: (data: Data) => void; 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 * @name useAsyncState
* @category State * @category State
@@ -25,35 +33,82 @@ export function useAsyncState<Data, Params extends any[] = [], Shallow extends b
maybePromise: Promise<Data> | ((...args: Params) => Promise<Data>), maybePromise: Promise<Data> | ((...args: Params) => Promise<Data>),
initialState: Data, initialState: Data,
options?: UseAsyncStateOptions<Shallow, Data>, options?: UseAsyncStateOptions<Shallow, Data>,
) { ): UseAsyncStateReturn<Data, Params, Shallow> {
const state = options?.shallow ? shallowRef(initialState) : ref(initialState); 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) if (options?.resetOnExecute)
state.value = initialState; 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; const promise = isFunction(maybePromise) ? maybePromise(...params as Params) : maybePromise;
try { try {
const data = await promise; const data = await promise;
state.value = data; state.value = data;
status.value = AsyncStateStatus.FULFILLED; isReady.value = true;
options?.onSuccess?.(data); options?.onSuccess?.(data);
} }
catch (error) { catch (e: unknown) {
status.value = AsyncStateStatus.REJECTED; error.value = e;
options?.onError?.(error); options?.onError?.(e);
if (options?.throwError) if (options?.throwError)
throw error; throw error;
} }
finally {
isLoading.value = false;
}
return state.value as Data; return state.value as Data;
}; };
const executeImmediately = (...params: Params) => {
return execute(0, ...params);
};
if (options?.immediate) if (options?.immediate)
execute(); 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);
},
}
} }