usePolling 轮询钩子封装分享

适用于Vue项目中的API进度实时追踪:

usePolling

import { ref, onMounted, onUnmounted, Ref, watch } from 'vue';

type UsePollingOptions<T> = {
  // 要轮询执行的异步函数
  asyncFunction: () => Promise<T>;
  // 轮询间隔时间(毫秒)
  interval: number;
  // 是否在初始化时立即执行一次
  immediate?: boolean;
  // 请求成功回调
  onSuccess?: (data: T) => void;
  // 请求失败回调
  onError?: (error: Error) => void;
  // 是否自动停止轮询的条件
  stopWhen?: (data: T) => boolean;
};

type UsePollingResult<T> = {
  // 最新请求数据
  data: Ref<T | null>;
  // 错误信息
  error: Ref<Error | null>;
  // 是否正在加载中
  isLoading: Ref<boolean>;
  // 是否正在轮询中
  isPolling: Ref<boolean>;
  // 开始轮询
  startPolling: () => void;
  // 停止轮询
  stopPolling: () => void;
  // 手动触发一次请求
  refresh: () => Promise<void>;
};

/**
 * 异步轮询组合式函数,用于API进度实时追踪
 * @param options 轮询配置选项
 * @returns 轮询状态和控制函数
 */
export function usePolling<T>({
  asyncFunction,
  interval,
  immediate = true,
  onSuccess,
  onError,
  stopWhen
}: UsePollingOptions<T>): UsePollingResult<T> {
  // 状态管理
  const data = ref<T | null>(null) as Ref<T | null>;
  const error = ref<Error | null>(null);
  const isLoading = ref<boolean>(false);
  const isPolling = ref<boolean>(false);

  // 定时器和取消控制器引用
  let timer: NodeJS.Timeout | null = null;
  let abortController: AbortController | null = null;

  // 执行异步请求
  const execute = async () => {
    // 如果已有请求,取消它
    if (abortController) {
      abortController.abort();
    }
    abortController = new AbortController();

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

    try {
      const result = await asyncFunction();
      data.value = result;
      onSuccess?.(result);

      // 检查是否需要停止轮询
      if (stopWhen && stopWhen(result)) {
        stopPolling();
      }
    } catch (err) {
      // 忽略取消错误
      if (err instanceof Error && err.name !== 'AbortError') {
        error.value = err;
        onError?.(err);
      }
    } finally {
      isLoading.value = false;
    }
  };

  // 开始轮询
  const startPolling = () => {
    if (isPolling.value) return;

    isPolling.value = true;

    // 立即执行一次还是等待第一个间隔
    if (immediate) {
      execute().then(() => {
        if (isPolling.value) {
          timer = setInterval(execute, interval);
        }
      });
    } else {
      timer = setInterval(execute, interval);
    }
  };

  // 停止轮询
  const stopPolling = () => {
    if (timer) {
      clearInterval(timer);
      timer = null;
    }
    if (abortController) {
      abortController.abort();
      abortController = null;
    }
    isPolling.value = false;
  };

  // 手动触发一次请求
  const refresh = async () => {
    // 如果正在轮询,先停止再重新开始
    const wasPolling = isPolling.value;
    if (wasPolling) {
      stopPolling();
    }

    await execute();

    // 如果之前在轮询,执行完后继续轮询
    if (wasPolling) {
      timer = setInterval(execute, interval);
      isPolling.value = true;
    }
  };

  // 组件挂载时初始化轮询
  onMounted(() => {
    if (immediate) {
      startPolling();
    }
  });

  // 组件卸载时清理
  onUnmounted(() => {
    stopPolling();
  });

  return {
    data,
    error,
    isLoading,
    isPolling,
    startPolling,
    stopPolling,
    refresh
  };
}

Vue组件中使用示例

<template>
  <div class="progress-tracker">
    <div v-if="isLoading">加载中...</div>
    <div v-else-if="error" class="error">{{ error.message }}</div>
    <div v-else-if="data">
      <p>当前进度: {{ data.progress }}%</p>
      <button @click="startPolling" :disabled="isPolling">开始轮询</button>
      <button @click="stopPolling" :disabled="!isPolling">停止轮询</button>
      <button @click="refresh">手动刷新</button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { usePolling } from '@/composables/usePolling';

// 模拟API请求函数
const fetchProgress = async () => {
  const response = await fetch('/api/progress');
  if (!response.ok) throw new Error('获取进度失败');
  return response.json();
};

// 使用轮询钩子
const {
  data,
  error,
  isLoading,
  isPolling,
  startPolling,
  stopPolling,
  refresh
} = usePolling({
  asyncFunction: fetchProgress,
  interval: 3000, // 3秒轮询一次
  immediate: true,
  // 当进度达到100%时自动停止轮询
  stopWhen: (data) => data.progress >= 100,
  onSuccess: (progress) => {
    console.log('进度更新:', progress);
    if (progress.progress >= 100) {
      alert('任务已完成!');
    }
  },
  onError: (error) => {
    console.error('轮询错误:', error);
  }
});
</script>

Hook 优势

  1. 自动清理机制:组件卸载时自动停止轮询并取消请求,避免内存泄漏
  2. 灵活的控制选项:支持stopWhen条件自动停止,refresh手动刷新
  3. 错误处理优化:区分普通错误和取消错误,避免误报
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一池勺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值