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
)