可控频率任务队列需控制触发节奏与执行顺序,基于节流思想实现带时间窗口的调度器,支持动态调频、暂停/恢复及任务取消。

要实现一个可控频率的函数执行任务队列,核心是控制任务的**触发节奏**和**执行顺序**,而不是简单地用 setTimeout 或 setInterval 堆砌。关键在于:任务入队后不立即执行,而是按设定的最小间隔(如每 200ms 最多执行一次)排队调度,同时支持动态调整频率、暂停/恢复、取消待执行任务。
用节流(throttle)思想构建队列调度器
这不是传统“先进先出”的纯队列,而是带时间窗口的调度器。每次添加任务时,判断距离上一次执行是否已过设定间隔;若未到,则暂存任务,等下次“窗口打开”时统一执行(或只执行最新一个,取决于策略)。
- 固定窗口 + 执行最新任务:适合防抖场景(如搜索框输入),只保留最后一次请求
- 固定窗口 + 执行队首任务:适合轮询类任务,保证每个任务至少被执行一次(可配最大等待时长)
-
动态频率调整:暴露
setRate(ms)方法,更新内部间隔阈值,后续调度按新频率生效
简易可运行实现(基于 Promise 队列)
以下是一个轻量、可暂停、可调频的任务队列示例:
class FrequencyQueue {
constructor(minInterval = 1000) {
this.minInterval = minInterval;
this.tasks = [];
this.lastExecTime = 0;
this.isRunning = true;
this.timer = null;
}
<p>push(fn, ...args) {
return new Promise((resolve) => {
this.tasks.push({ fn, args, resolve });
this._schedule();
});
}</p><p>_schedule() {
if (!this.isRunning || this.timer || this.tasks.length === 0) return;</p><pre class='brush:php;toolbar:false;'>const now = Date.now();
const nextAllowed = this.lastExecTime + this.minInterval;
const delay = Math.max(0, nextAllowed - now);
this.timer = setTimeout(() => {
const task = this.tasks.shift();
if (task) {
try {
const result = task.fn(...task.args);
task.resolve(Promise.resolve(result));
} catch (e) {
task.resolve(Promise.reject(e));
}
this.lastExecTime = Date.now();
}
this.timer = null;
this._schedule(); // 继续调度下一个
}, delay);}
setRate(ms) { this.minInterval = Math.max(0, ms); }
pause() { this.isRunning = false; if (this.timer) { clearTimeout(this.timer); this.timer = null; } }
resume() { this.isRunning = true; this._schedule(); }
clear() { this.tasks = []; if (this.timer) { clearTimeout(this.timer); this.timer = null; } } }
// 使用示例: const q = new FrequencyQueue(500); // 每 500ms 最多执行一个任务
q.push(console.log, 'task 1').then(() => console.log('done 1')); q.push(console.log, 'task 2').then(() => console.log('done 2')); q.push(console.log, 'task 3').then(() => console.log('done 3'));
// 运行中动态降频 setTimeout(() => q.setRate(1200), 2000);
进阶要点:避免常见陷阱
-
时间精度问题:
Date.now()和setTimeout本身有延迟,高频场景建议用requestIdleCallback或queueMicrotask做微调,但注意兼容性 -
错误隔离:单个任务异常不应阻塞整个队列,需在
try/catch中包裹执行,并确保resolve/reject总被调用 -
内存泄漏风险:长期运行的队列需提供
clear()或自动清理超时任务(例如设置maxWait: 5000,入队超 5 秒未执行则 reject) -
与事件循环协同:若任务本身是异步(如 API 调用),应在
fn返回 Promise 后再触发下一次调度,而非仅看“开始执行”时间
替代方案参考
实际项目中,也可基于成熟库快速搭建:
- Lodash throttle / debounce:适合简单节流,但不提供队列管理和动态调频接口
-
p-queue(npm):专注并发控制,配合自定义
interval可模拟频率限制 -
RxJS 的
concatMap+delay:适合响应式流场景,天然支持取消与频率变换

















