Newer
Older
smart-home-server / webclient-vue / src / composables / useAsyncRequest.js
import { ref } from "vue";

/**
 * Composable for managing async request lifecycle:
 * isLoading, error, and AbortController cancellation.
 *
 * @param {Object} options
 * @param {boolean} options.ignoreTimeout – when true (default), timeout errors
 *   are NOT written to `error` because they usually come from request
 *   cancellation on route leave / modal close.
 */
export function useAsyncRequest(options = {}) {
  const abortController = ref(null);
  const isLoading = ref(false);
  const error = ref(null);

  async function execute(apiCall) {
    abortController.value?.abort();
    const controller = new AbortController();
    abortController.value = controller;

    isLoading.value = true;
    error.value = null;

    try {
      const result = await apiCall(controller.signal);
      abortController.value = null;
      isLoading.value = false;

      if (!result.ok) {
        if (options.ignoreTimeout !== false && result.error?.type === "timeout") {
          return result;
        }
        error.value = result.error;
        return result;
      }

      return result;
    } catch (err) {
      abortController.value = null;
      isLoading.value = false;
      error.value = {
        type: "network_error",
        message: err?.message || "Network error",
      };
      return { ok: false, error: error.value };
    }
  }

  function abort() {
    abortController.value?.abort();
    abortController.value = null;
  }

  function clear() {
    error.value = null;
    abort();
  }

  return {
    abortController,
    isLoading,
    error,
    execute,
    abort,
    clear,
  };
}