需求
在使用循环任务时,往往需要使用到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
)
结果:
案例一
无
案例二
案例三