Vue3中循环任务优化方案

发布时间 2023-06-14 13:06:52作者: ScarlettK

需求

在使用循环任务时,往往需要使用到setInterval方法。其接受三个参数,分别是 1.具体执行的函数 2.执行时间间隔 3.传递个函数的参数,并返回一个id,后续可以使用这个id来停止循环的执行。具体的使用可以查阅MDN
在实际开发中,很容易重复创建相同的interval实例,进行反复的执行,并且在轮询场景,如果间隔时间设置不当,往往上一条请求未完成,下一条执行又开始了。
并且在Vue3中,由于热更新的存在更容易导致不断的创建interval实例。
为了解决这个问题可以使用下面的方法

解决方案

useSchedule.ts

const _id = Date.now();

class Schedule {

  id: number;

  intervalId: number;

  /**
   * 待执行任务集合
   */
  tasks: Map<string, Task> = new Map<string, Task>();

  constructor(id: number) {
    this.id = id;
    this.intervalId = setInterval(() => {
          // 校验本Schedule是否有效 无效则直接销毁
          if (this.id !== _id) {
            this.destroy();
            return;
          }
          // 有效则遍历Task列表 找到合适的执行
          const now = Date.now();
          for (const task of this.tasks.values()) {
            task.tryExecute(now);
          }
        },
        1
    )
    console.log("Red_K.", `开始Schedule! id: ${this.id}, intervalId: ${this.intervalId}`)
  }

  setDelayTask(id: string, exec: Function, delay: number) {
    this.tasks.set(
        id,
        new Task(
            id,
            exec,
            () => {
              this.removeTask(id)
            },
            "Delay",
            delay
        ));
  }

  setLoopTask(id: string, exec: Function, interval: number, delay: number = 0) {
    this.tasks.set(
        id,
        new Task(
            id,
            exec,
            () => {
              this.removeTask(id)
            },
            "Loop",
            delay,
            interval
        )
    )
  }

  setAsyncLoopTask(id: string, exec: Function, interval: number, delay: number = 0) {
    this.tasks.set(
        id,
        new Task(
            id,
            exec,
            () => {
              this.removeTask(id)
            },
            "Loop",
            delay,
            interval,
            false
        )
    )
  }

  removeTask(id: string) {
    this.tasks.delete(id);
    console.debug("Red_K.", `id: ${id} 的Task被移除!`)
  }

  private destroy() {
    clearInterval(this.intervalId);
    this.tasks.clear();
    console.log("Red_K.", `id: ${this.id}, intervalId: ${this.intervalId} 的Schedule示例被销毁!`)
  }
}

class Task {

  // 任务id
  id: string;

  // 具体运行函数
  exec: Function;

  // 任务类型
  type: "Delay" | "Loop";

  // 延时时间 延时任务中有效
  delay: number;

  // 间隔时间 循环任务中有效
  interval: number;

  // 是否正在运行
  isRunning: boolean = false;

  // 下次执行时间
  next: number;

  // 是否是同步任务。 尽在循环任务中有效,在循环任务中,如果前一个任务未执行完,又到其执行时间,跳过本次执行。
  isSync: boolean;

  // 延时任务在执行后需要移除自己
  removeSelf: Function;

  constructor(id: string,
              exec: Function,
              removeSelf: Function,
              type: "Delay" | "Loop",
              delay: number = 0,
              interval: number = 1000,
              isSync: boolean = true) {
    this.id = id;
    this.exec = exec;
    this.removeSelf = removeSelf;
    this.type = type;
    this.delay = delay;
    this.interval = interval;
    this.isSync = isSync;
    this.next = Date.now() + this.delay;
  }

  tryExecute(now: number) {
    if (now < this.next || (this.isSync && this.isRunning)) {
      return;
    }
    this.execute();
    if (this.type === 'Loop') {
      this.next = Date.now() + this.interval;
    }
    else if (this.type === 'Delay') {
      this.removeSelf();
    }
  }

  private async execute() {
    this.isRunning = true;
    await this.exec();
    this.isRunning = false;
  }

}
const schedule = new Schedule(_id);
export useSchedule(){
  return {
    schedule 
  }
}

具体使用案例

import { useSchedule } form '@/use/core/useSchedule'
const schedule = useSchedule().schedule;
// 案例1: 延时任务

schedule.setDelayTask(
  "testDelay",
  () => {console.log('10秒后执行')},
  10000
)

// 案例2: 同步循环任务
// 假设任务执行时长为10秒 但是间隔设置为5秒。则会在前一个任务执行完毕之后再去执行后一个任务。

let time = Date.now();
async function exec() {
  await new Promise<void>((resolve) => {
    setTimeout(() => {
      const now = Date.now();
      const pass = now - time;
      time = now;
      console.log(`测试同步循环任务, 间隔时间是 ${pass / 1000}s`)
      resolve();
    }, 5000)
  });
}

schedule.setLoopTask(
    "syncLoopTest",
    exec,
    3000
)

// 案例3: 异步循环任务
// 假设任务执行时长为10秒 但是间隔设置为5秒。则会在前一个任务执行完毕之后再去执行后一个任务。

let time = Date.now();
async function exec() {
  await new Promise<void>((resolve) => {
    setTimeout(() => {
      const now = Date.now();
      const pass = now - time;
      time = now;
      console.log(`测试异步循环任务, 间隔时间是 ${pass / 1000}s`)
      resolve();
    }, 5000)
  });
}

schedule.setAsyncLoopTask(
    "asyncLoopTest",
    exec,
    3000
)

结果:
案例一

案例二

案例三