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

相信大家做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;
相关推荐
labixiong17 小时前
还原一个完整符合规范的 Promise(二)
前端·javascript
时光足迹17 小时前
腾讯云 TRTC UniApp SDK 从入门到上线
前端·vue.js·uni-app
时光足迹17 小时前
uni-app 里把加密视频嵌入页面播放?我分析了 4 种方案,只有 1 种接近完美
前端·vue.js·uni-app
To_OC18 小时前
万字解析《JS 语言精粹》之第五章:继承 5 大核心精髓(JS 原型核心)
前端·javascript·代码规范
时光足迹18 小时前
极光推送全攻略(上):被iOS证书折磨了三天,我写了一份前端也能看懂的避坑指南
前端·ios·uni-app
DyLatte18 小时前
AI 时代,最危险的不是被替代,而是努力不沉淀
前端·后端·程序员
mCell18 小时前
【锐评】桌面端技术营销:别拿跑分当工程判断
前端·rust·electron
柒和远方19 小时前
从一次工程审查看 AI 学习产品的边界兜底:RAG 资料链路一致性实战
前端·后端·架构
疯狂的魔鬼19 小时前
一个"懂分寸"的文本省略组件是怎样炼成的
前端·vue.js·设计
angerdream19 小时前
手把手编写儿童手机远程监控App之vue3 AI Gent
前端