设计一个多计时任务的任务管理功能

前言

我们想写一个功能,能够不停的添加计时任务执行,且每个任务都支持自定义延迟时间做某件事件,平时我们可能直接setTimeout就完事了,有多个少个任务开启多少个setTimeout,可是这样真的行么,同时开启这么多timeout不仅仅延迟会很大,性能也会有折扣

实际上我们只使用一个setTimeout计时器,就可以实现这种多个自定义延迟时间的任务功能

实现步骤

只需要下面的步骤即可:

  1. 待办任务处理:将待办任务存放到队列中,根据延迟时间计算出最终执行时间戳,任务可有序,可无序,处理方式不同,推荐有序,处理起来更简单快速(这里也是按照有序处理)
  2. 延迟处理:执行延迟任务时,满足执行条件的最小时间戳先执行,然后观察后续最小的时间戳时间间隔,setTimeout计时器根据时间差设置延迟,以此类推
  3. 任务执行实际:任务放入队列的时候,如果没设置延迟,直接执行即可,如果设置了延迟则入队,如果是队列第一个,则需要设置延迟计时器,当然后续进入的计时器抢到了第一个,还需要更新计时器,因此需要清理过去的计时器,重新计时;如果不是,则什么都不需要处理

代码实现

实现代码如下所示,我们写一个简易的任务管理系统

js 复制代码
class Task {
  // delay;
  // timestamp;
  // fn;
  constructor(fn, delay = 0) {
    this.fn = fn;
    this.delay = delay;
    this.timestamp = Date.now() + delay;
  }
}

class Queue {
  constructor() {
    this.queue = [];
  }

  enter(task) {
    let idx = 0;
    for (const item of this.queue) {
      if (item.timestamp > task.timestamp) {
        break;
      }
      idx++;
    }
    this.queue.splice(idx, 0, task);
    return idx;
  }

  leave() {
    return this.queue.shift();
  }
}

class TaskManager {
  constructor() {
    this.queue = new Queue();
    this.timeout = null;
  }

  addTask(task) {
    if (!task.delay) {
      task.fn && task.fn();
      return;
    }
    if (this.queue.enter(task) === 0) {
      //新增加的非立即执行最早的任务,回更新timeout计时器延迟,因此需要清理之前的计时器
      this.timeout && clearTimeout(this.timeout);
      this.doDelayTask(task.delay);
    }
  }

  doDelayTask(delay) {
    this.timeout = setTimeout(() => {
      let next = null;
      do {
        const current = this.queue.leave();
        current.fn && current.fn();
        next = this.queue.queue[0];
        if (!next) break;
        const delay = next.timestamp - Date.now();
        if (delay > 0) {
          this.doDelayTask(delay);
          break;
        }
      } while (next);
    }, delay);
  }
}

执行以下测试案例

js 复制代码
let first = Date.now();
function testInterval() {
  let idx = 1;
  
  let timeout = setInterval(() => {
    console.log("time", idx++, "间隔", Date.now() - first);
    if (idx > 100) {
      clearInterval(timeout);
    }
  }, 1000);
}

function testManager() {
  const manager = new TaskManager();
  manager.addTask(
    new Task(() => {
      console.log("task1,1s后执行", Date.now() - first);
    }, 1000),
  );
  manager.addTask(
    new Task(() => {
      console.log("task2,1s后执行", Date.now() - first);
    }, 1000),
  );
  manager.addTask(
    new Task(() => {
      console.log("task3,3s后执行", Date.now() - first);
    }, 3000),
  );
  manager.addTask(
    new Task(() => {
      console.log("task4,5s后执行", Date.now() - first);
    }, 5000),
  );
  manager.addTask(
    new Task(() => {
      console.log("task5,7s后执行", Date.now() - first);
    }, 7000),
  );
  manager.addTask(
    new Task(() => {
      console.log("task6,9s后执行", Date.now() - first);
    }, 9000),
  );
}

//计时用于更好观看结果
testInterval();
testManager();

使用vscode的nodejs环境打印如下

js 复制代码
time 1 间隔 1004
task1,1s后执行 1008
task2,1s后执行 1008
time 2 间隔 2006
task3,3s后执行 3014
time 3 间隔 3014
time 4 间隔 4017
task4,5s后执行 5009
time 5 间隔 5025
time 6 间隔 6030
task5,7s后执行 7007
time 7 间隔 7038
time 8 间隔 8044
task6,9s后执行 9005
time 9 间隔 9050
time 10 间隔 10058

也可以看到nodejs的setInterval的机制和浏览器的不一样,nodejs的会持续叠加时间间隔,而浏览器环境会修正(个人测试了哈)

优化建议

上面的优化相对比较好优化的就是那个队列了,可以换成链表的方式,理论应该能够提一点速度,使用比较稳定

当然,这个实际就有点考验自己的基本功了(如果平时不怎么碰算法的智慧写业务的话),实际也不难

原理大概就是,如果操作很频繁的话,那么频繁操作数组则性能则会相对低一些,毕竟数组线性的,频繁插入删除,数组可能频繁创建和移动,相比较链表频发插入删除场景,链表理论效率更高

我们写一个吧,当然可以用可不用,理论和实践还是有差距的,只是演示一个思路

js 复制代码
//链表
class linkedList {
  constructor() {
    this.linkedList = null;
  }

  enter(task) {
    //空链直接给表头
    if (!this.linkedList) {
      this.linkedList = task;
      return 0;
    }
    //只有一个节点,且新节点时间戳小于当前节点时间戳,则新节点成为表头
    let current = this.linkedList;
    if (task.timestamp < current.timestamp) {
      this.linkedList = task;
      task.next = current;
      return 0;
    }
    //新节点不会成为表头,遍历链表找到插入位置
    let idx = 1;
    let pre = current;
    let current = pre.next;
    while (current) {
      if (task.timestamp < current.timestamp) {
        pre.next = task;
        task.next = current;
        return idx;
      }
      pre = current;
      current = current.next;
      idx++;
    }
    //到最后了
    pre.next = task;
    return idx;
  }

  leave() {
    if (!this.linkedList) return
    this.linkedList = this.linkedList.next;
  }
}

最后

就介绍到这里吧,我们写点小东西,也避免被ai的洪流吃掉我们思考的脑子😄

相关推荐
ModyQyW1 小时前
vite-plugin-uni-manifest 更新了什么
前端·typescript·uni-app
Asize2 小时前
AI 协作开发新范式:我用 SDD 做了个排版 npm 包
前端·人工智能
kyriewen3 小时前
我把今年流传的前端 AI 面试题整理了一遍——4 类场景题+回答框架(附速查表)
前端·面试·程序员
计算机魔术师4 小时前
国产多模态模型正面硬刚Opus旗舰:差距从30%缩到3%
前端
风骏时光牛马4 小时前
程序员进阶:深度思考,解锁职场成长的底层逻辑
前端
IT_陈寒4 小时前
用了Proxy才发现以前的JavaScript白写了
前端·人工智能·后端
爱丶不疚5 小时前
Eval: Agent 说的 Eval 是什么?从单测、TDD 到 Sentry 聊起
前端·ai编程·vibecoding
求道於盲5 小时前
python中的类型标注
前端
计算机魔术师5 小时前
从硅谷测试到全球铺开,ChatGPT广告的10亿美元秘密
前端