相信大家做pc端开发的时候都会遇到一个需求,比如扫码登录的时候,通过手机端扫码之后一般都需要通过轮询查询登录状态,其实这是个很常见的需求,但是没做过的小伙伴可能就会不经意间写出一个bug。就是比如1秒钟轮询一次的时候,如果上次的接口还没成功响应那么下一次的接口就又发送出去了。正确做法应该是等待上个响应后再发出下一次请求。
其实解决bug的关键就是如何等待上次请求响应之后才发出下一次的请求,等待
具体应该怎么实现呢?可以参考下我的思路。
具体如下:
ts
type PollingEvent = 'success' | 'error' | 'stop';
interface PollingOptions {
interval?: number; // 轮询间隔时间
retries?: number; // 接口失败后重试次数
immediate?: boolean; // 是否立即调用一次接口
}
type EventCallback<T> = (data?: T | Error) => void;
class PollingService<T = any> {
private requestFn: () => Promise<T>;
private interval: number;
private retries: number;
private immediate: boolean;
private timer: ReturnType<typeof setTimeout> | null = null;
private isPolling: boolean = false;
private currentRetry: number = 0;
private listeners = new Map<PollingEvent, Set<EventCallback<T>>>();
constructor(requestFn: () => Promise<T>, options: PollingOptions = {}) {
this.requestFn = requestFn;
this.interval = options.interval || 1500;
this.retries = options.retries || 0;
this.immediate = options.immediate ?? true;
}
on(event: PollingEvent, callback: EventCallback<T>): () => void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
const listeners = this.listeners.get(event)!;
listeners.add(callback);
return () => listeners.delete(callback);
}
start(): void {
if (this.isPolling) return;
this.isPolling = true;
this.currentRetry = 0;
if (this.immediate) {
this.execute();
} else {
this.scheduleNext();
}
}
stop(): void {
this.isPolling = false;
this.clearTimer();
this.emit('stop');
}
private async execute(): Promise<void> {
if (!this.isPolling) return;
// 其实关键就在这里了,我们借助 async/await来实现
try {
const data = await this.requestFn();
this.currentRetry = 0;
this.emit('success', data);
} catch (error) {
this.handleError(error as Error);
return;
}
this.scheduleNext();
}
private handleError(error: Error): void {
this.emit('error', error);
if (this.currentRetry < this.retries) {
this.currentRetry++;
this.execute(); // 立即重试
return;
}
this.stop();
}
private scheduleNext(): void {
if (!this.isPolling) return;
this.clearTimer();
this.timer = setTimeout(() => {
this.execute();
}, this.interval);
}
private clearTimer(): void {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
}
private emit(event: PollingEvent, data?: T | Error): void {
const listeners = this.listeners.get(event);
if (listeners) {
listeners.forEach((callback) => callback(data));
}
}
}
export { PollingService };
完成上述代码之后,在页面中直接引入使用即可。
使用如下:
ts
import React, { useEffect, useRef } from 'react';
import { PollingService } from 'xxx/PollingService';
const Demo = () => {
const pollingRef = useRef<any>(null);
// 轮询的方法
const requesFun = async () => {
const res = await axios.post('xxx')
// todo sth
}
useEffect(() => {
pollingRef.current = new PollingService()
// 启动轮询
pollingRef.current.start()
// !!!组件卸载一定要关闭轮询
return () => {
pollingRef?.current?.stop()
}
}, [)
<div>测试页面</div>
}
export default Demo;