在前端开发中,调度器(Scheduler)是控制任务执行顺序、管理任务执行节奏的核心工具,它可以解决任务并发控制、优先级排序、避免主线程阻塞等问题。JS作为单线程语言,调度器的实现需要依托事件循环和任务队列机制,下面从多个场景讲解调度器的实现方式。

基础任务调度器实现
最基础的调度器可以实现任务的顺序执行,将任务加入队列后,按照添加顺序依次执行。我们可以使用数组作为任务队列,通过标识控制当前是否有任务在执行。
class BaseScheduler {
constructor() {
// 任务队列,存储待执行的任务
this.taskQueue = [];
// 标识当前是否有任务正在执行
this.isRunning = false;
}
// 添加任务到队列
addTask(task) {
this.taskQueue.push(task);
// 如果当前没有任务执行,启动调度
if (!this.isRunning) {
this.run();
}
}
// 执行队列中的任务
async run() {
this.isRunning = true;
while (this.taskQueue.length > 0) {
// 取出队列头部的任务
const task = this.taskQueue.shift();
try {
// 执行任务,支持异步任务
await task();
} catch (err) {
console.error('任务执行失败:', err);
}
}
this.isRunning = false;
}
}
// 使用示例
const scheduler = new BaseScheduler();
// 添加三个异步任务
scheduler.addTask(() => new Promise(resolve => setTimeout(() => {
console.log('任务1执行完成');
resolve();
}, 1000)));
scheduler.addTask(() => new Promise(resolve => setTimeout(() => {
console.log('任务2执行完成');
resolve();
}, 500)));
scheduler.addTask(() => new Promise(resolve => setTimeout(() => {
console.log('任务3执行完成');
resolve();
}, 800)));
// 执行顺序为任务1、任务2、任务3,每个任务等待前一个执行完成后再执行
并发控制调度器实现
实际场景中我们经常需要限制同时执行的任务数量,比如同时最多执行2个任务,避免过多任务占用资源。这种调度器需要维护一个正在执行的任务池,当任务池未满时,从队列中取出任务执行,任务完成后补充新任务。
class ConcurrencyScheduler {
constructor(maxConcurrency) {
// 最大并发数量
this.maxConcurrency = maxConcurrency;
// 待执行任务队列
this.waitingQueue = [];
// 正在执行的任务列表
this.runningTasks = [];
}
// 添加任务
addTask(task) {
return new Promise((resolve, reject) => {
// 将任务和对应的resolve、reject存入队列
this.waitingQueue.push({
task,
resolve,
reject
});
// 尝试执行任务
this.tryRun();
});
}
// 尝试执行任务
tryRun() {
// 如果达到最大并发数,或者没有待执行任务,直接返回
if (this.runningTasks.length >= this.maxConcurrency || this.waitingQueue.length === 0) {
return;
}
// 取出队列头部的任务
const { task, resolve, reject } = this.waitingQueue.shift();
// 执行任务
const taskPromise = Promise.resolve().then(() => task());
// 将任务加入正在执行的列表
this.runningTasks.push(taskPromise);
// 任务执行完成后处理结果,并从执行列表中移除
taskPromise.then(
res => {
resolve(res);
this.cleanRunningTask(taskPromise);
},
err => {
reject(err);
this.cleanRunningTask(taskPromise);
}
);
// 递归尝试执行下一个任务
this.tryRun();
}
// 从执行列表中清理完成的任务
cleanRunningTask(taskPromise) {
const index = this.runningTasks.indexOf(taskPromise);
if (index > -1) {
this.runningTasks.splice(index, 1);
}
// 清理完成后尝试执行下一个任务
this.tryRun();
}
}
// 使用示例:最大并发数为2
const concurrencyScheduler = new ConcurrencyScheduler(2);
// 创建5个模拟任务
const tasks = Array.from({ length: 5 }, (_, i) => {
return () => new Promise(resolve => {
const time = Math.random() * 1000 + 500;
setTimeout(() => {
console.log(`任务${i + 1}执行完成,耗时${time.toFixed(0)}ms`);
resolve(`任务${i + 1}结果`);
}, time);
});
});
// 添加所有任务
tasks.forEach(task => {
concurrencyScheduler.addTask(task).then(res => console.log(res));
});
优先级调度器实现
当任务有优先级区分时,需要让高优先级任务先执行。我们可以维护一个优先级队列,任务添加时按照优先级排序,执行时优先取出优先级高的任务。这里使用简单的数组排序实现优先级队列,实际场景可以使用更高效的二叉堆结构。
class PriorityScheduler {
constructor() {
// 优先级队列,存储{ priority, task }结构
this.priorityQueue = [];
// 是否正在执行任务
this.isRunning = false;
}
// 添加任务,priority值越小优先级越高
addTask(task, priority = 0) {
this.priorityQueue.push({
priority,
task
});
// 按照优先级排序,优先级高的排在前面
this.priorityQueue.sort((a, b) => a.priority - b.priority);
if (!this.isRunning) {
this.run();
}
}
// 执行任务
async run() {
this.isRunning = true;
while (this.priorityQueue.length > 0) {
// 取出优先级最高的任务
const { task } = this.priorityQueue.shift();
try {
await task();
} catch (err) {
console.error('优先级任务执行失败:', err);
}
}
this.isRunning = false;
}
}
// 使用示例
const priorityScheduler = new PriorityScheduler();
// 添加不同优先级的任务,priority值越小优先级越高
priorityScheduler.addTask(() => new Promise(resolve => setTimeout(() => {
console.log('低优先级任务执行');
resolve();
}, 500)), 2);
priorityScheduler.addTask(() => new Promise(resolve => setTimeout(() => {
console.log('高优先级任务执行');
resolve();
}, 300)), 0);
priorityScheduler.addTask(() => new Promise(resolve => setTimeout(() => {
console.log('中优先级任务执行');
resolve();
}, 400)), 1);
// 执行顺序为高优先级、中优先级、低优先级
时间切片调度器实现
为了避免长时间执行的JS任务阻塞主线程,导致页面卡顿,我们可以使用时间切片的方式,将长任务拆分成多个小任务,每执行一小段时间就交出主线程控制权,等待下一个事件循环再继续执行。这种调度器常用于React的Fiber架构等场景。
class TimeSliceScheduler {
constructor(sliceTime = 16) {
// 每个时间切片的长度,默认16ms,接近屏幕刷新率
this.sliceTime = sliceTime;
// 待执行的任务切片队列
this.taskSlices = [];
// 是否正在调度
this.isScheduling = false;
}
// 添加任务,任务可以是返回多个切片的生成器函数
addTask(taskGenerator) {
const iterator = taskGenerator();
this.taskSlices.push(iterator);
if (!this.isScheduling) {
this.schedule();
}
}
// 调度执行
schedule() {
this.isScheduling = true;
// 使用requestIdleCallback在浏览器空闲时执行,降级使用setTimeout
const idleCallback = window.requestIdleCallback || (cb => setTimeout(cb, this.sliceTime));
idleCallback((deadline) => {
this.runTask(deadline);
});
}
// 执行任务切片
runTask(deadline) {
// 判断是否有剩余时间,或者是否超时
while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && this.taskSlices.length > 0) {
const iterator = this.taskSlices[0];
const { value, done } = iterator.next();
if (done) {
// 当前生成器执行完成,移除
this.taskSlices.shift();
}
// 如果还有任务,继续调度
if (this.taskSlices.length > 0) {
this.schedule();
} else {
this.isScheduling = false;
}
}
}
}
// 使用示例:长任务拆分
const timeSliceScheduler = new TimeSliceScheduler();
// 生成器函数,拆分长任务
function* longTask() {
const data = Array.from({ length: 1000 }, (_, i) => i);
for (let i = 0; i < data.length; i++) {
// 每个切片处理10条数据
if (i % 10 === 0) {
console.log(`处理到第${i}条数据`);
yield; // 交出控制权
}
// 模拟数据处理
data[i] = data[i] * 2;
}
console.log('长任务处理完成');
}
timeSliceScheduler.addTask(longTask);
不同调度器的适用场景
不同的调度器适用于不同的业务场景,我们可以根据需求选择合适的实现:
- 基础任务调度器:适合需要顺序执行、不需要并发控制的简单任务场景
- 并发控制调度器:适合接口请求、资源加载等需要限制同时执行数量的场景,避免资源耗尽
- 优先级调度器:适合有任务优先级区分的场景,比如用户交互任务优先级高于数据统计任务
- 时间切片调度器:适合大型计算、大量DOM操作等可能阻塞主线程的场景,提升页面流畅度
注意事项
在实现JS调度器时需要注意几个问题:
- 异步任务的处理:需要使用
await或者Promise确保任务执行完成后再进行下一步调度 - 错误捕获:任务执行过程中可能出现错误,需要做好错误捕获,避免调度流程中断
- 性能优化:优先级队列如果使用数组排序,任务数量多时性能会下降,建议使用二叉堆等数据结构优化
- 浏览器兼容性:时间切片调度器用到的
requestIdleCallback在部分浏览器不支持,需要做好降级处理
JS_Scheduler任务调度时间切片优先级调度异步任务修改时间:2026-06-19 05:00:30