核心实现:
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,
};
};