/** * useAsyncState Composable * * Manages loading/error/success states for async operations * Consolidates pattern used across 13+ components * * @example * const { execute, loading, error, data, isSuccess } = useAsyncState(); * await execute(async () => { * return await fetchSomeData(); * }); */ import { ref, computed } from 'vue'; export function useAsyncState(options = {}) { const { initialData = null, onSuccess = null, onError = null, maxRetries = 0, retryDelay = 1000 } = options; const loading = ref(false); const error = ref(null); const data = ref(initialData); const abortController = ref(null); const isSuccess = computed( () => !loading.value && !error.value && data.value !== null ); const isError = computed(() => !loading.value && error.value !== null); const isIdle = computed( () => !loading.value && error.value === null && data.value === null ); /** * Execute an async function with state management * @param {Function} asyncFn - Async function to execute * @param {Object} executeOptions - Options for this execution * @returns {Promise} Result of async function */ async function execute(asyncFn, executeOptions = {}) { const { retries = maxRetries, signal = null } = executeOptions; // Create abort controller if not provided if (!signal) { abortController.value = new AbortController(); } loading.value = true; error.value = null; let lastError; let attempt = 0; while (attempt <= retries) { try { const result = await asyncFn(signal || abortController.value?.signal); data.value = result; loading.value = false; if (onSuccess) { onSuccess(result); } return result; } catch (err) { lastError = err; // Don't retry if aborted if (err.name === 'AbortError') { break; } attempt++; // If more retries remaining, wait before retrying if (attempt <= retries) { await new Promise(resolve => setTimeout(resolve, retryDelay * attempt) ); } } } // All retries exhausted error.value = lastError; loading.value = false; if (onError) { onError(lastError); } throw lastError; } /** * Cancel the current async operation */ function cancel() { if (abortController.value) { abortController.value.abort(); abortController.value = null; } loading.value = false; } /** * Reset state to initial values */ function reset() { cancel(); loading.value = false; error.value = null; data.value = initialData; } return { // State loading, error, data, // Computed isSuccess, isError, isIdle, // Methods execute, cancel, reset }; }