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

作者:剪刀石头布啊日期:2026/9/2

前言

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

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

实现步骤

只需要下面的步骤即可:

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

代码实现

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

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的洪流吃掉我们思考的脑子😄


《设计一个多计时任务的任务管理功能》 是转载文章,点击查看原文。


相关推荐


AI 想放弃了,人没有 -- 肘子的 Swift 周报 #150
东坡肘子2026/8/25

AI 想放弃了,人没有 最近,Linus Torvalds 在调试一个 Intel Xe GPU 驱动问题时,经历了一场被他自己称为“debug session from hell”的漫长排查。最终的修复简单得有些不可思议:把一处 round_up() 改成 round_down()。但为了找到这一行代码,他前后添加了 24 个调试 patch,启动了 18 次 kernel。其中大量繁琐工作,都是在 AI 的帮助下完成的。 这个故事最有意思的地方并不是“AI 帮 Linus 修复了 Linux


DeepSeek API涨价观察:AI行业从免费到收费的转折点
Yoyo Chen1802026/8/12

北大燕姐 · 商业观察 8月6日,DeepSeek一纸公告,AI圈炸了。 短短几行字:API服务价格将整体上调,预计涨幅较大。评论区一片哀嚎,四个字刷了屏:AI免费时代,结束了。 别慌。恰恰相反,我想说一句反话:普通人的机会,来了。 只是机会从来不会平均分配,它只给看得懂的人。这半年,北大燕姐帮企业主和内容创作者做IP定位,见过太多人:工具用得很溜,方向一塌糊涂。AI涨价这一天,恰好是一面照妖镜,照出谁在裸泳,谁早有准备。 一、先把事件捋清楚 8月1日:单日Token处理量破8万亿,


第三人称——骑马系统以及交互动画
神龙天舞20012026/8/3

第三人称——骑马系统以及交互动画 大家好,今天我们来聊一个在游戏开发里特别有“内味儿”的话题——第三人称视角下的骑马系统,以及那些让玩家觉得“哇,这马好听话”的交互动画。骑马不是简单的“把角色模型放到马背上”,它涉及到动画状态机、物理交互、摄像机跟随、甚至还有“骑手重心偏移”这种细节。这篇文章我会用最接地气的方式,把核心逻辑拆开揉碎,配上能跑的代码示例,让你看完就能上手做原型。## 一、骑马系统的核心:不是“骑上去”,而是“合体”很多新手做骑马,就是把玩家角色(Humanoid)的骨骼绑定到马(


Go Modules 包管理详解:go.mod、go.sum 与依赖管理
程序员爱钓鱼2026/7/26

Go 编程实战系列(五) 上一篇《第一个 Go 程序:Hello World》中,我们创建了第一个 Go 项目,并执行过这样一条命令: go mod init hello-go 执行完成后,项目目录中会出现一个 go.mod 文件。 很多初学者第一次看到它时可能会疑惑: go.mod 到底有什么用?Go 项目的第三方库从哪里下载?为什么有时还会出现 go.sum?go get 和 go mod tidy 又有什么区别? 这些问题都与 Go Modules 有关。 Go Modules 是现代


Alibaba买家账号运营指南:注册流程、账号养护及多账号管理实践
xiaobaihuoke2026/7/18

在跨境电商行业中,很多企业把重点放在卖家账号运营、产品推广以及广告投放上,但实际上,一个稳定、高质量的买家账号,同样是跨境运营体系中不可忽视的重要资源。 无论是进行海外市场调研、寻找供应商、分析行业趋势,还是获取产品报价,一个真实、活跃的Alibaba国际站买家号,都能够帮助企业更加深入了解市场。 然而,随着Alibaba平台风控机制不断完善,买家账号的运营已经不再是简单注册即可使用。账号注册后的行为习惯、活跃程度以及采购轨迹,都会影响账号后续的使用价值。 因此,买家号运营逐渐形成了一个完


再见Fable 5,OpenAI出手了!GPT-5.6真香~
AI袋鼠帝2026/7/10

大家好,我是袋鼠帝 我最近在疯狂使用Fable 5,好用是好用,就是太贵啦! 不过不是在Claude 官方使用的,因为我最新的一个号,在7月1号又tm被封了,服了都(已经记不清是第几个了) 天下苦A社久矣.. 所以,我最近都是在Cursor上用的Fable 5,虽然我下血本充了200$的订阅,但跑了两天Fable 5,总用量就已经超过48%了。。。 整的我都有点不敢用了,除非核心任务,其他时候我都只敢开启auto模式(自动选择模型) 就是这么憋屈。 然后今天凌晨,OpenAI终于出手了! G


从零构建ReAct智能体:让AI学会边想边做
树獭非懒2026/7/2

前言 大语言模型已经能写诗、编程、做数学题,但它有一个根本性的短板:无法与外部世界交互。你问它"今天深圳天气怎么样",它只能诚恳地告诉你"我的知识截止到某某日期"——不是它不想回答,是它根本没有"看天气"这个动作能力。 这就是智能体(Agent)要解决的核心问题:让 LLM 不仅能想,还能做。 业界现有的智能体框架(LangChain、LlamaIndex 等)已经将这套机制封装得非常完善,但它们的高度抽象也让很多人停留在"调包侠"阶段,对背后的运行机制一知半解。本文的目标,是带你从零构建一个


Swift 还让你 Excited 吗?-- 肘子的 Swift 周报 #141
东坡肘子2026/6/23

Swift 还让你 Excited 吗? 上周,Reddit 上一篇题为《Is anyone else excited by Swift progress as the language?》的帖子引发了不少讨论。WWDC 2026 上,Apple 明确表示,Swift 已被用于 WebKit 关键组件、QUIC 网络栈、字体渲染、驱动与固件。发帖者据此认为,Swift 正在向核心操作系统内核迈进,并将这一幕视为 2014 年"高级系统编程语言"承诺的兑现。评论区的讨论则很快从“是否兑现了承诺”,


别再让业务层裸奔 CarPropertyManager 了!谈谈汽车车载核心服务的架构封装
潜龙勿用之化骨龙2026/6/15

前言 Android Automotive 项目中,CarPropertyManager 几乎是访问一切车辆状态的入口。但在真实项目里,如果直接在 ViewModel 或业务层大量使用它,代码会变得难以维护、难以测试,也完全不符合 现代Android 架构 的分层思想。 本文将从零开始,梳理一套基于 Clean Architecture 的 CarPropertyManager 封装方案,涵盖: 为什么不能直接用 如何用抽象基类统一封装 callbackFlow 的正确姿势 Flow 与 su


「装闭」-AI驱动的开源装修闭坑系统
fthux2026/6/8

愿每个业主在装修路上心中有数,于是,「装闭」来了。 装修行业的水有点儿深 很多人装修完回头看,都发现自己花了不少冤枉钱。设计师画的效果图美轮美奂——复杂吊顶、满墙背景墙、开放格书架……装好以后才发现,这些东西费钱不说,日常打理还特别费劲。吊顶积灰、开放格藏污纳垢、花哨灯带一年到头开不了几次。 更别说合同和报价单里的猫腻。增项漏项、模糊工艺描述、虚报材料数量——这些套路装修公司玩得轻车熟路,但普通业主根本无从分辨。随便一个增项,几千块甚至上万块就多出来了。 于是就有了「装闭」(RenoPit)这

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读