Vue3中循环任务优化方案

需求

在使用循环任务时,往往需要使用到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(`开始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
        )
    )
  }

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

  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(`id: ${id} 的Task被移除!`)
  }

  private destroy() {
    clearInterval(this.intervalId);
    this.tasks.clear();
    console.log(`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;

  eager: boolean;

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

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

  tryExecute(now: number) {
    if (this.isSync && this.isRunning) {
      if (!this.eager && this.type === 'Loop') {
        this.next = now + this.interval;
      }
      return;
    }
    if (now < this.next) {
      return;
    }
    this.execute();
    if (this.type === 'Loop') {
      this.next = 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 function useSchedule() {
  return {
    schedule
  }
}

具体使用案例

案例一: 延时任务

import { useSchedule } from "@/use/sys/useSchedule";

const schedule = useSchedule().schedule;

let lastTime = Date.now();

async function mockFn() {
  await new Promise<void>((resolve) => {
    setTimeout(() => {
      const now = Date.now();
      const realInterval = now - lastTime;
      lastTime = now;
      console.log(`Test delay task, real interval is ${realInterval / 1000}s`)
      resolve();
    }, 4000)
  })
}

schedule.setDelayTask(
    "delayTest",
    mockFn,
    2000
)

案例二:循环任务(同步非eager)

import { useSchedule } from "@/use/sys/useSchedule";

const schedule = useSchedule().schedule;

let lastTime = Date.now();

async function mockFn() {
  await new Promise<void>((resolve) => {
    setTimeout(() => {
      const now = Date.now();
      const realInterval = now - lastTime;
      lastTime = now;
      console.log(`Test Normal Loop task, real interval is ${realInterval / 1000}s`)
      resolve();
    }, 4000)
  })
}

schedule.setLoopTask(
    "LoopTest",
    mockFn,
    2000
)

案例三:eager循环任务

import { useSchedule } from "@/use/sys/useSchedule";

const schedule = useSchedule().schedule;

let lastTime = Date.now();

async function mockFn() {
  await new Promise<void>((resolve) => {
    setTimeout(() => {
      const now = Date.now();
      const realInterval = now - lastTime;
      lastTime = now;
      console.log(`Test Eager Loop task, real interval is ${realInterval / 1000}s`)
      resolve();
    }, 4000)
  })
}

schedule.setEagerLoopTask(
    "EagerLoopTest",
    mockFn,
    2000
)

案例四:异步循环

import { useSchedule } from "@/use/sys/useSchedule";

const schedule = useSchedule().schedule;

let lastTime = Date.now();

async function mockFn() {
  await new Promise<void>((resolve) => {
    setTimeout(() => {
      const now = Date.now();
      const realInterval = now - lastTime;
      lastTime = now;
      console.log(`Test Async Loop task, real interval is ${realInterval / 1000}s`)
      resolve();
    }, 4000)
  })
}

schedule.setAsyncLoopTask(
    "AsyncLoopTest",
    mockFn,
    2000
)

posted @ 2023-06-14 13:02  ScarlettK  阅读(290)  评论(0编辑  收藏  举报