轮询事件hook

69 阅读2分钟

核心实现:

1、Promise事件处理机制

2、useRef: 一开始所有的AI吐出来的方案,都是用useState来管理intervalId,但是这玩意儿异步了嘛,startPolling执行的时候,intervalId值没有更新,没法stop,改成useRef来管理,就同步了

3、useEffect 的 return,在组件卸载时停止轮询

import { useCallback, useEffect, useRef } from 'react';

/** 轮询Hook */
const useManualPolling = (
  requestFunction: (...args: any) => Promise<any>,
  interval: number = 1000,
) => {
  const intervalId = useRef<any>(null);

  const stopPolling = useCallback(() => {
    if (intervalId?.current) {
      clearInterval(intervalId?.current);
      intervalId.current = null;
    }
  }, [intervalId.current]);

  const startPolling = useCallback(
    (params?: any) => {
      return new Promise((resolve, reject) => {
        if (intervalId.current) return; // 如果已经在轮询,则不再启动
        // 失败,并停止轮询
        const onFail = (res: any) => {
          stopPolling();
          reject(res);
        };
        // 成功,并停止轮询
        const onSuccess = (res: any) => {
          stopPolling();
          resolve(res);
        };
        const fetchData = async () => {
          await requestFunction(params, onSuccess, onFail);
        };
        fetchData(); // 初次请求
        const id = setInterval(fetchData, interval); // 开启轮询
        intervalId.current = id;
      });
    },
    [requestFunction, interval, intervalId.current],
  );

  // 在组件卸载时停止轮询
  useEffect(() => {
    return () => {
      stopPolling();
    };
  }, [stopPolling]);

  return startPolling;
};

export default useManualPolling;

使用方法:

/** 导出结果-轮询 */
  const queryExportUrl = useManualPolling(async (params, onSuccess, onFail) => {
    try {
      const res = await queryExportFile(params);
      const { success, data } = res;
      if (!success || data?.exportStatus === 'FAIL') {
        message.error('导出失败,请稍后重试');
        onFail(res);
      }
      if (data?.exportStatus === 'SUCCESS') {
        message.success('导出完成,请点击下载');
        onSuccess(res);
      }
    } catch (err) {
      onFail({ success: false });
    }
  }, 10000);

  /** 导出 */
  const exportOrderList = async (
    requestParams: GAMEMC_API.QueryOrderListRequest,
  ) => {
    try {
      message.success('开始导出,请稍等……');
      // 轮询-导出结果
      const exportRes = await queryExportUrl(requestParams);
      return exportRes;
    } catch (error) {
      return { success: false };
    }
  };
  

备注:

1、结合react渲染、usehook更新、和原生js闭包的原理,生成一个执行流程分析?

2、同useRequest的轮询方案对比;

useRequest的轮询方案:


export const usePollingPay = ({
  handleResultSuccess,
  handleCreateFail,
  handleResultFail,
  handleCreateSuccess,
}: UsePollingPayProps) => {
  const { appName } = useParams();

  const payResult = useRef<any>(null);

  // 创建订单
  const { run: createOrder, data: createRes } = useRequest(
    async (params: any) => {
      return await Create(params, {
        params: { name: appName },
      });
    },
    {
      manual: true,
      onError: (res: any) => {
        // 直接 返回游戏报错回调,不会拉起支付二维码
        handleCreateFail(res);
      },
      onSuccess: (res: any) => {
        // 2、 打开付款二维码
        const { data } = res;
        // eslint-disable-next-line @typescript-eslint/no-use-before-define
        handleCreateSuccess(data, cancelPollingRequestPay);
        // 3、轮询-支付结果,判断是否支付完成
        // @ts-ignore
        // eslint-disable-next-line @typescript-eslint/no-use-before-define
        pollingRequestPay({ tradeNo: data?.tradeNo });
      },
    },
  );

  // 轮询-支付结果
  const { run: pollingRequestPay, cancel: cancelPollingRequestPay } =
    useRequest(
      async (params: any) => {
        return await QueryOrder({
          ...params,
          name: appName,
        });
      },
      {
        manual: true,
        onError: (res: any) => {
          payResult.current = res;
          cancelPollingRequestPay();
          handlePayFail(res);
        },
        onSuccess: (res: any) => {
          payResult.current = res;
          // 4、判断 支付结果
          const payStatus = res?.data?.payStatus;
          if (res?.success && payStatus !== 'WAITING_PAY') {
            // 结束轮询
            cancelPollingRequestPay();
            if (payStatus === 'PAY_SUCCESS') {
              handlePaySuccess(res);
            } else {
              handlePayFail(res);
            }
          }
        },
        pollingInterval: 1000,
      },
    );

  return {
    createOrder,
    cancelPollingRequestPay,
  };
};