Files
memory-infrastructure-palace/code/websites/pokedex.online/src/composables/useAsyncState.js

137 lines
2.8 KiB
JavaScript

/**
* 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<any>} 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
};
}