如何更好的封装一个接口轮询?

相信大家做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;
相关推荐
于慨1 天前
Lambda 表达式、方法引用(Method Reference)语法
java·前端·servlet
石小石Orz1 天前
油猴脚本实现生产环境加载本地qiankun子应用
前端·架构
从前慢丶1 天前
前端交互规范(Web 端)
前端
CHU7290351 天前
便捷约玩,沉浸推理:线上剧本杀APP功能版块设计详解
前端·小程序
GISer_Jing1 天前
Page-agent MCP结构
前端·人工智能
王霸天1 天前
💥别再抄网上的Scale缩放代码了!50行源码教你写一个永不翻车的大屏适配
前端·vue.js·数据可视化
小领航1 天前
用 Three.js + Vue 3 打造炫酷的 3D 行政地图可视化组件
前端·github
@大迁世界1 天前
2026年React大洗牌:React Hooks 将迎来重大升级
前端·javascript·react.js·前端框架·ecmascript
PieroPc1 天前
一个功能强大的 Web 端标签设计和打印工具,支持服务器端直接打印到局域网打印机。Fastapi + html
前端·html·fastapi
悟空瞎说1 天前
深入 Vue3 响应式:为什么有的要加.value,有的不用?从设计到源码彻底讲透
前端·vue.js