在JavaScript中,可以通过以下步骤实现一个队列来控制并发任务的顺序执行:
实现思路
- 队列结构:使用数组存储待执行任务。
- 并发控制:设置最大并发数,跟踪当前运行的任务数。
- 任务调度:当有任务完成时,从队列中取出下一个任务执行,确保按添加顺序启动。
代码实现
class ConcurrentQueue {
constructor(concurrency = 1) {
this.concurrency = concurrency; // 最大并发数
this.running = 0; // 当前运行中的任务数
this.queue = []; // 待执行的任务队列
}
// 添加任务到队列
add(task) {
this.queue.push(task);
this.next();
}
// 执行下一个任务
next() {
// 当未达到并发上限且队列中有任务时
while (this.running < this.concurrency && this.queue.length > 0) {
const task = this.queue.shift(); // 取出队列中的第一个任务
this.running++;
task().finally(() => {
this.running--;
this.next(); // 当前任务完成后继续调度
});
}
}
}
使用示例
// 示例:模拟异步任务
const delay = (ms, msg) =>
new Promise(resolve => setTimeout(() => {
console.log(msg);
resolve();
}, ms));
// 创建并发数为2的队列
const queue = new ConcurrentQueue(2);
// 添加任务(按顺序启动)
queue.add(() => delay(1000, '任务1完成'));
queue.add(() => delay(500, '任务2完成'));
queue.add(() => delay(800, '任务3完成'));
queue.add(() => delay(200, '任务4完成'));
/* 输出顺序:
"任务2完成" (500ms后)
"任务1完成" (1000ms后)
"任务4完成" (1200ms后,任务1完成后启动)
"任务3完成" (1300ms后,任务2完成后启动)
*/
关键点解析
- 按序启动:通过
shift()
从队列头部取出任务,确保先添加的任务先执行。 - 并发控制:
while
循环保证任何时候运行的任务数不超过concurrency
。 - 自动调度:每个任务完成后自动触发
next()
,继续执行后续任务。
适用场景
- API请求限速(如避免触发反爬机制)
- 文件批量处理(如控制同时打开的文件数)
- 异步任务流水线(如按顺序处理但允许并行加速)
如果需要严格按完成顺序处理结果,需额外逻辑(如收集结果后排序),但此实现已满足基本并发控制需求。
要实现任务的严格顺序执行(即前一个任务完成后才能开始下一个),无论任务本身的执行时间长短,需要将并发数设置为 1
,即改为串行队列。以下是修改后的实现:
class SerialQueue {
constructor() {
this.queue = []; // 任务队列
this.isRunning = false; // 当前是否有任务在执行
}
// 添加任务到队列
add(task) {
this.queue.push(task);
this.run();
}
// 执行任务(严格串行)
async run() {
if (this.isRunning) return;
this.isRunning = true;
while (this.queue.length > 0) {
const task = this.queue.shift(); // 按顺序取任务
await task(); // 等待当前任务完成
}
this.isRunning = false;
}
}
使用示例
const queue = new SerialQueue();
queue.add(() => delay(1000, '任务1完成'));
queue.add(() => delay(500, '任务2完成'));
queue.add(() => delay(800, '任务3完成'));
queue.add(() => delay(200, '任务4完成'));
/* 输出顺序(严格按添加顺序完成):
"任务1完成" (1000ms后)
"任务2完成" (1500ms后)
"任务3完成" (2300ms后)
"任务4完成" (2500ms后)
*/
关键变化说明
- 去掉了并发控制:强制每次只执行一个任务,通过
await
等待当前任务完成。 - 执行逻辑简化:使用
while
循环逐个执行任务,确保绝对顺序。
如果你需要 **并发执行但按顺序处理结果
如果希望任务可以并行执行(如并发网络请求),但结果处理按顺序进行,可以使用以下方案:
class OrderedResultQueue {
constructor() {
this.queue = []; // 任务队列
this.nextOrder = 0; // 期待的下一个任务序号
this.results = new Map(); // 存储已完成任务的结果
}
// 添加任务(需自带序号)
add(task, order) {
this.queue.push({ task, order });
this.run();
}
// 执行任务(可并行)
async run() {
// 所有任务并行执行
const promises = this.queue.map(async ({ task, order }) => {
const result = await task();
this.results.set(order, result);
});
await Promise.all(promises);
this.queue = [];
// 按顺序输出结果
while (this.results.has(this.nextOrder)) {
console.log(this.results.get(this.nextOrder));
this.results.delete(this.nextOrder);
this.nextOrder++;
}
}
}
使用示例
const queue = new OrderedResultQueue();
// 添加任务时需指定顺序标识
queue.add(() => delay(1000, '任务1完成'), 1);
queue.add(() => delay(500, '任务2完成'), 2);
queue.add(() => delay(800, '任务3完成'), 3);
queue.add(() => delay(200, '任务4完成'), 4);
/* 输出顺序:
"任务1完成" (1000ms后)
"任务2完成" (1000ms后,但等待任务1完成后一起按顺序输出)
"任务3完成"
"任务4完成"
*/
方案对比
方案 | 执行方式 | 结果顺序 | 适用场景 |
---|---|---|---|
SerialQueue | 串行执行 | 严格顺序 | 必须前一个任务完成后才能开始下一个 |
OrderedResultQueue | 并行执行 | 按序处理 | 任务可并行但结果需要按顺序使用 |
根据你的实际需求选择合适的实现方式。