✨ Add composable for managing asynchronous state
This commit is contained in:
130
code/websites/pokedex.online/src/composables/useAsyncState.js
Normal file
130
code/websites/pokedex.online/src/composables/useAsyncState.js
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user