前言
我们想写一个功能,能够不停的添加计时任务执行,且每个任务都支持自定义延迟时间做某件事件,平时我们可能直接setTimeout就完事了,有多个少个任务开启多少个setTimeout,可是这样真的行么,同时开启这么多timeout不仅仅延迟会很大,性能也会有折扣
实际上我们只使用一个setTimeout计时器,就可以实现这种多个自定义延迟时间的任务功能
实现步骤
只需要下面的步骤即可:
- 待办任务处理:将待办任务存放到队列中,根据延迟时间计算出最终执行时间戳,任务可有序,可无序,处理方式不同,推荐有序,处理起来更简单快速(这里也是按照有序处理)
- 延迟处理:执行延迟任务时,满足执行条件的最小时间戳先执行,然后观察后续最小的时间戳时间间隔,setTimeout计时器根据时间差设置延迟,以此类推
- 任务执行实际:任务放入队列的时候,如果没设置延迟,直接执行即可,如果设置了延迟则入队,如果是队列第一个,则需要设置延迟计时器,当然后续进入的计时器抢到了第一个,还需要更新计时器,因此需要清理过去的计时器,重新计时;如果不是,则什么都不需要处理
代码实现
实现代码如下所示,我们写一个简易的任务管理系统
1class Task { 2 // delay; 3 // timestamp; 4 // fn; 5 constructor(fn, delay = 0) { 6 this.fn = fn; 7 this.delay = delay; 8 this.timestamp = Date.now() + delay; 9 } 10} 11 12class Queue { 13 constructor() { 14 this.queue = []; 15 } 16 17 enter(task) { 18 let idx = 0; 19 for (const item of this.queue) { 20 if (item.timestamp > task.timestamp) { 21 break; 22 } 23 idx++; 24 } 25 this.queue.splice(idx, 0, task); 26 return idx; 27 } 28 29 leave() { 30 return this.queue.shift(); 31 } 32} 33 34class TaskManager { 35 constructor() { 36 this.queue = new Queue(); 37 this.timeout = null; 38 } 39 40 addTask(task) { 41 if (!task.delay) { 42 task.fn && task.fn(); 43 return; 44 } 45 if (this.queue.enter(task) === 0) { 46 //新增加的非立即执行最早的任务,回更新timeout计时器延迟,因此需要清理之前的计时器 47 this.timeout && clearTimeout(this.timeout); 48 this.doDelayTask(task.delay); 49 } 50 } 51 52 doDelayTask(delay) { 53 this.timeout = setTimeout(() => { 54 let next = null; 55 do { 56 const current = this.queue.leave(); 57 current.fn && current.fn(); 58 next = this.queue.queue[0]; 59 if (!next) break; 60 const delay = next.timestamp - Date.now(); 61 if (delay > 0) { 62 this.doDelayTask(delay); 63 break; 64 } 65 } while (next); 66 }, delay); 67 } 68} 69
执行以下测试案例
1let first = Date.now(); 2function testInterval() { 3 let idx = 1; 4 5 let timeout = setInterval(() => { 6 console.log("time", idx++, "间隔", Date.now() - first); 7 if (idx > 100) { 8 clearInterval(timeout); 9 } 10 }, 1000); 11} 12 13function testManager() { 14 const manager = new TaskManager(); 15 manager.addTask( 16 new Task(() => { 17 console.log("task1,1s后执行", Date.now() - first); 18 }, 1000), 19 ); 20 manager.addTask( 21 new Task(() => { 22 console.log("task2,1s后执行", Date.now() - first); 23 }, 1000), 24 ); 25 manager.addTask( 26 new Task(() => { 27 console.log("task3,3s后执行", Date.now() - first); 28 }, 3000), 29 ); 30 manager.addTask( 31 new Task(() => { 32 console.log("task4,5s后执行", Date.now() - first); 33 }, 5000), 34 ); 35 manager.addTask( 36 new Task(() => { 37 console.log("task5,7s后执行", Date.now() - first); 38 }, 7000), 39 ); 40 manager.addTask( 41 new Task(() => { 42 console.log("task6,9s后执行", Date.now() - first); 43 }, 9000), 44 ); 45} 46 47//计时用于更好观看结果 48testInterval(); 49testManager(); 50
使用vscode的nodejs环境打印如下
1time 1 间隔 1004 2task1,1s后执行 1008 3task2,1s后执行 1008 4time 2 间隔 2006 5task3,3s后执行 3014 6time 3 间隔 3014 7time 4 间隔 4017 8task4,5s后执行 5009 9time 5 间隔 5025 10time 6 间隔 6030 11task5,7s后执行 7007 12time 7 间隔 7038 13time 8 间隔 8044 14task6,9s后执行 9005 15time 9 间隔 9050 16time 10 间隔 10058 17
也可以看到nodejs的setInterval的机制和浏览器的不一样,nodejs的会持续叠加时间间隔,而浏览器环境会修正(个人测试了哈)
优化建议
上面的优化相对比较好优化的就是那个队列了,可以换成链表的方式,理论应该能够提一点速度,使用比较稳定
当然,这个实际就有点考验自己的基本功了(如果平时不怎么碰算法的智慧写业务的话),实际也不难
原理大概就是,如果操作很频繁的话,那么频繁操作数组则性能则会相对低一些,毕竟数组线性的,频繁插入删除,数组可能频繁创建和移动,相比较链表频发插入删除场景,链表理论效率更高
我们写一个吧,当然可以用可不用,理论和实践还是有差距的,只是演示一个思路
1 2//链表 3class linkedList { 4 constructor() { 5 this.linkedList = null; 6 } 7 8 enter(task) { 9 //空链直接给表头 10 if (!this.linkedList) { 11 this.linkedList = task; 12 return 0; 13 } 14 //只有一个节点,且新节点时间戳小于当前节点时间戳,则新节点成为表头 15 let current = this.linkedList; 16 if (task.timestamp < current.timestamp) { 17 this.linkedList = task; 18 task.next = current; 19 return 0; 20 } 21 //新节点不会成为表头,遍历链表找到插入位置 22 let idx = 1; 23 let pre = current; 24 let current = pre.next; 25 while (current) { 26 if (task.timestamp < current.timestamp) { 27 pre.next = task; 28 task.next = current; 29 return idx; 30 } 31 pre = current; 32 current = current.next; 33 idx++; 34 } 35 //到最后了 36 pre.next = task; 37 return idx; 38 } 39 40 leave() { 41 if (!this.linkedList) return 42 this.linkedList = this.linkedList.next; 43 } 44} 45
最后
就介绍到这里吧,我们写点小东西,也避免被ai的洪流吃掉我们思考的脑子😄
《设计一个多计时任务的任务管理功能》 是转载文章,点击查看原文。