Skip to content

开发说明/源码

基于基础类,polling.ts,衍生 vu3 、react 版本

基础类,polling.ts

ts
/**
 * 轮询配置选项接口
 */
export interface IPollingOptions<T = unknown> {
  /** 轮询函数 */
  pollingFn: () => Promise<T>;
  /** 轮询间隔(毫秒),默认1000ms */
  interval?: number;
  /** 最大轮询次数,默认100次 */
  maxCount?: number;
  /** 是否立即执行,默认true */
  immediate?: boolean;
  /** 轮询条件,返回false时停止轮询 */
  condition?: () => boolean;
  /** 轮询成功回调 */
  onSuccess?: (data: T) => void;
  /** 轮询失败回调 */
  onError?: (error: unknown) => void;
  /** 轮询完成回调(达到最大次数或条件不满足时) */
  onComplete?: () => void;
  /** 轮询状态回调 */
  onStatus?: (status: IPollingStatus) => void;
}

/**
 * 轮询状态接口
 */
export interface IPollingState<T = unknown> {
  /** 是否正在轮询 */
  isPolling: boolean;
  /** 是否暂停 */
  isPaused: boolean;
  /** 轮询次数 */
  count: number;
  /** 轮询数据 */
  data: T | null;
  /** 轮询错误 */
  error: unknown;
}

/**
 * 轮询控制方法接口
 */
export interface IPollingControls {
  /** 开始轮询 */
  startPolling: () => void;
  /** 停止轮询 */
  stopPolling: () => void;
  /** 重置轮询 */
  resetPolling: () => void;
  /** 暂停轮询 */
  pausePolling: () => void;
  /** 恢复轮询 */
  resumePolling: () => void;
  /** 销毁轮询 */
  destroy: () => void;
}

const DEFAULT_OPTIONS: Required<IPollingOptions<unknown>> = {
  pollingFn: () => Promise.resolve(null),
  interval: 1000,
  maxCount: 100,
  immediate: true,
  condition: () => true,
  onSuccess: () => {},
  onError: () => {},
  onComplete: () => {},
  onStatus: () => {},
};

export enum IPollingStatus {
  START = "start",
  PAUSE = "pause",
  STOP = "stop",
  COMPLETE = "complete",
  TIMEOUT = "timeout",
}

// ========================轮询工具=======================

/**
 * 框架无关的轮询工具类
 */
export class PollingManager<T = unknown>
  implements IPollingState<T>, IPollingControls
{
  private _isPolling = false;
  private _isPaused = false;
  private _count = 0;
  private _timer: ReturnType<typeof setInterval> | null = null;
  private _data: T | null = null;
  private _error: unknown = null;
  private readonly options: Required<IPollingOptions<T>>;

  constructor(options: IPollingOptions<T>) {
    this.options = { ...DEFAULT_OPTIONS, ...options };

    if (this.options.immediate) {
      this.startPolling();
    }
  }

  // Getters
  get isPolling(): boolean {
    return this._isPolling;
  }
  get isPaused(): boolean {
    return this._isPaused;
  }
  get count(): number {
    return this._count;
  }
  get data(): T | null {
    return this._data;
  }
  get error(): unknown {
    return this._error;
  }

  /**
   * 执行轮询
   */
  private async polling(): Promise<void> {
    if (this._isPaused) return;

    // 检查轮询条件
    if (!this.options.condition()) {
      this.log("轮询条件不满足,结束轮询");
      this.stopPolling();
      this.options.onComplete();
      this.options.onStatus(IPollingStatus.COMPLETE);
      return;
    }

    // 检查最大轮询次数
    if (this._count >= this.options.maxCount) {
      this.log("达到最大轮询次数");
      this.stopPolling();
      this.options.onComplete();
      this.options.onStatus(IPollingStatus.TIMEOUT);
      return;
    }

    try {
      this._count++;
      this._data = await this.options.pollingFn();
      this._error = null;
      this.options.onSuccess(this._data);
      this.log(`轮询成功 #${this._count}`);
    } catch (err) {
      this._error = err;
      this.options.onError(err);
      this.log(`轮询失败 #${this._count}`, err);
    }
  }

  /**
   * 开始轮询
   */
  startPolling(): void {
    if (this._isPolling) {
      this.log("轮询已在运行中");
      return;
    }

    this.log("开始轮询");
    this._isPolling = true;
    this._isPaused = false;
    this._count = 0;
    this._error = null;

    // 清除之前的定时器
    if (this._timer) {
      clearInterval(this._timer);
    }

    this._timer = setInterval(() => {
      if (!this._isPolling || this._isPaused) return;
      this.polling();
    }, this.options.interval);

    this.options.onStatus(IPollingStatus.START);
  }

  /**
   * 停止轮询
   */
  stopPolling(): void {
    this.log("停止轮询");
    this._isPolling = false;
    this._isPaused = false;

    if (this._timer) {
      clearInterval(this._timer);
      this._timer = null;
    }

    this.options.onStatus(IPollingStatus.STOP);
  }

  /**
   * 重置轮询
   */
  resetPolling(): void {
    this.log("重置轮询");
    this.stopPolling();
    this.startPolling();
  }

  /**
   * 暂停轮询
   */
  pausePolling(): void {
    if (!this._isPolling || this._isPaused) return;

    this.log("暂停轮询");
    this._isPaused = true;
    this.options.onStatus(IPollingStatus.PAUSE);
  }

  /**
   * 恢复轮询
   */
  resumePolling(): void {
    if (!this._isPolling || !this._isPaused) return;

    this.log("恢复轮询");
    this._isPaused = false;
    this.options.onStatus(IPollingStatus.START);
  }

  /**
   * 日志记录
   */
  private log(message: string, data?: unknown): void {
    const timestamp = new Date().toLocaleTimeString();
    const logData = data ? ` | ${JSON.stringify(data)}` : "";
    console.log(`[PollingManager ${timestamp}] ${message}${logData}`);
  }

  /**
   * 销毁轮询管理器
   */
  destroy(): void {
    this.log("销毁轮询管理器");
    this.stopPolling();
    this.options.onStatus(IPollingStatus.COMPLETE);
  }
}

/**
 * 创建轮询管理器的工厂函数
 */
export function createPolling<T = unknown>(
  options: IPollingOptions<T>
): PollingManager<T> {
  return new PollingManager(options);
}

vue3 版本 hook,usePollingVue3.ts

ts
import { onMounted, onUnmounted, ref, type Ref } from "vue";
import {
  createPolling,
  IPollingStatus,
  type IPollingOptions,
  type PollingManager,
} from "./polling";

/**
 * Vue3轮询Hook
 * @param options 轮询配置选项
 * @returns 轮询相关的状态和方法
 */
export function usePolling<T = unknown>(options: IPollingOptions<T>) {
  const pollingManager = ref<PollingManager<T> | null>(null); // 轮询管理器
  const isPolling = ref(false); // 是否正在轮询
  const isPaused = ref(false); // 是否暂停
  const count = ref(0); // 轮询次数
  const data = ref<T | null>(null); // 轮询数据
  const error = ref<unknown>(null); // 轮询错误

  // 创建轮询管理器
  const createManager = () => {
    // 先清理现有的管理器
    if (pollingManager.value) {
      pollingManager.value.destroy();
      pollingManager.value = null;
    }

    const manager = createPolling({
      ...options,
      onSuccess: (result: T) => {
        data.value = result;
        error.value = null;
        options.onSuccess?.(result);
      },
      onError: (err: unknown) => {
        error.value = err;
        options.onError?.(err);
      },
      onComplete: () => {
        options.onComplete?.();
      },
      onStatus: (status: IPollingStatus) => {
        options.onStatus?.(status);
      },
    });

    // 监听状态变化
    const updateState = () => {
      isPolling.value = manager.isPolling;
      isPaused.value = manager.isPaused;
      count.value = manager.count;
      data.value = manager.data;
      error.value = manager.error;
    };

    // 创建定时器来同步状态
    const syncTimer = setInterval(updateState, 100);

    // 保存引用
    pollingManager.value = manager;

    // 返回清理函数
    return () => {
      clearInterval(syncTimer);
      manager.destroy();
    };
  };

  // 控制方法
  const startPolling = () => {
    if (!pollingManager.value) {
      createManager();
    }
    pollingManager.value?.startPolling();
  };

  const stopPolling = () => {
    pollingManager.value?.stopPolling();
  };

  const resetPolling = () => {
    pollingManager.value?.resetPolling();
  };

  const pausePolling = () => {
    pollingManager.value?.pausePolling();
  };

  const resumePolling = () => {
    pollingManager.value?.resumePolling();
  };

  // 销毁轮询管理器
  const destroy = () => {
    if (pollingManager.value) {
      pollingManager.value.destroy();
      pollingManager.value = null;
    }
    // 重置状态
    isPolling.value = false;
    isPaused.value = false;
    count.value = 0;
    data.value = null;
    error.value = null;

    console.log("destroy---", pollingManager.value);
  };

  onMounted(() => {
    // 是否立即轮询
    if (options.immediate !== false) {
      createManager();
    }
  });

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

  return {
    isPolling,
    isPaused,
    count,
    data,
    error,
    startPolling,
    stopPolling,
    resetPolling,
    pausePolling,
    resumePolling,
    destroy,
    // 手动创建管理器的方法(用于延迟创建)
    createManager,
  };
}

Released under the ISC License.