订单页轮询(setInterval中包含异步操作)的思路

238 阅读2分钟

背景

最近在做一个B端的项目, 其中订单页需要做一个轮询的处理, 能让用户在不刷新网页的情况下实时看到最新的订单状态. 项目使用的是vue2.7 + ts(装饰器)

想法

  • 初始版本: 这里最开始想的比较简单, 直接用setInterval进行处理就好了. 由于使用的是keep-alive组件, 所以在activated中开启定时, 在deactivated中clearInterval就ok. 大概代码如下:

    clearInterval(this.timer);
    await this.getList();
    this.timer = setInterval(async () => {
      console.log('init');
      await this.getList();
    }, 5000);
    

    不过实际操作起来, 这种方式的局限性太大, 尤其是包含了异步操作. 在实际的场景中, 如果这个操作触发的过于频繁(比如分页会重新触发, 搜索会重新触发), 就很有可能上一次的setInterval没有成功清除, 导致页面数据错乱.

  • 优化: 我注意到setTimeout可以很好的解决异步带来的问题, 于是封装了一个方法, 用于专门解决这类问题. 大概思路如下:

    1. 要实现setInterval的效果
    2. 需要可以自由控制暂停,开启
    3. 传入的(异步)函数可能有参数 下面是具体的代码:
export class RepeatedAsyncExecutor<T> {
  private readonly asyncFn: (...args: T[]) => Promise<void>;
  private readonly interval: number;
  private timer: number | null = null;
  private running = false;
  private args: T[];

  constructor(asyncFn: (...args: T[]) => Promise<void>, interval: number) {
    this.asyncFn = asyncFn;
    this.interval = interval;
    this.args = [];
  }

  private async _execute() {
    if (!this.running) return;

    try {
      await this.asyncFn(...this.args);
    } catch (error) {
      console.error('Error in async function:', error);
    } finally {
      if (this.running) {
        this.timer = setTimeout(() => this._execute(), this.interval);
      }
    }
  }

  public start(...args: T[]) {
    if (this.running) return;
    this.running = true;
    this.args = args;
    this.timer = setTimeout(() => this._execute(), this.interval);
  }

  public stop() {
    this.running = false;
    if (this.timer) {
      clearTimeout(this.timer);
      this.timer = null;
    }
  }

  public restart(...args: T[]) {
    this.stop();
    this.start(...args);
  }
}

// 使用示例
 const asyncFn = async (message: string) => {
   await new Promise<void>(resolve => setTimeout(resolve, 2000));
   console.log('Async function executed:', message, new Date().toISOString());
 };

 const executor = new RepeatedAsyncExecutor<string>(asyncFn, 5000);
 executor.start('Hello World');

// 如果需要停止执行,可以调用
 executor.stop();

// 如果需要重新开始并更改参数(例如分页操作后),可以调用
 executor.restart('New Message');

注意

这里的中断并不能中断已经开始执行的代码, 运行中代码依然会继续执行. 如果确实需要中断异步代码, 可以通过锁的机制来处理.