你在电商大促时同时发起100个商品图片上传请求,服务器不堪重负直接崩溃------这就是并发控制的生死时刻!本文将用Promise打造一个智能任务队列,让异步任务像收费站车流一样高效运转。
为何需要任务队列?
当我们面对大量异步任务时:
javascript
// 灾难现场:同时发起100个请求
for (let i = 0; i < 100; i++) {
fetch(`/api/upload?img=product-${i}.jpg`)
}
痛点分析:
- 服务器压力:瞬间100个并发请求可能压垮服务端
- 浏览器限制:Chrome最多允许6个同域名并发请求
- 资源竞争:过多任务同时执行导致内存/CPU暴涨
方案对比:
方案 | 优势 | 劣势 | 适用场景 |
---|---|---|---|
无控制直接并发 | 实现简单 | 易引发雪崩效应 | 少量安全请求 |
Promise.all | 批量处理 | 无并发限制 | 小批量任务 |
Promise队列 | 精准流量控制 | 需额外实现 | 高并发场景 |
核心实现:智能限流控制器
1. 类结构设计 - 三个核心成员
javascript
class TaskQueue {
constructor(maxConcurrent) {
this.maxConcurrent = maxConcurrent; // 最大并发数
this.currentCount = 0; // 当前进行中的任务数
this.pendingQueue = []; // 等待队列
}
// 添加任务到队列(核心方法)
enqueue(taskFunc) {
// 实现见下文
}
}
2. 任务调度逻辑 - 收费站模型
javascript
enqueue(taskFunc) {
return new Promise((resolve, reject) => {
// 封装原始任务(添加执行上下文)
const wrappedTask = async () => {
try {
this.currentCount++;
const result = await taskFunc(); // 执行实际任务
resolve(result); // 传递结果
} catch (error) {
reject(error); // 传递错误
} finally {
this.currentCount--;
this._runNext(); // 完成后触发下个任务
}
};
// 决策逻辑:立即执行 or 加入等待
if (this.currentCount < this.maxConcurrent) {
wrappedTask();
} else {
this.pendingQueue.push(wrappedTask);
}
});
}
// 执行队列中的下一个任务
_runNext() {
if (this.pendingQueue.length === 0) return;
if (this.currentCount >= this.maxConcurrent) return;
const nextTask = this.pendingQueue.shift();
nextTask();
}
实战场景:电商图片上传系统
1. 模拟上传函数
javascript
// 模拟带随机失败率的图片上传
const mockUpload = (id) => () =>
new Promise((resolve, reject) => {
const delay = 500 + Math.random() * 1000;
setTimeout(() => {
Math.random() > 0.2
? resolve(`图片${id}上传成功`)
: reject(`图片${id}上传超时`);
}, delay);
});
2. 队列化上传 - 安全执行
javascript
const uploadQueue = new TaskQueue(3); // 最大并发3
// 批量添加15个上传任务
const tasks = [];
for (let i = 1; i <= 15; i++) {
tasks.push(uploadQueue.enqueue(mockUpload(i)));
}
// 全局结果处理
Promise.allSettled(tasks).then(results => {
const success = results.filter(r => r.status === 'fulfilled');
console.log(`成功率: ${success.length}/${results.length}`);
});
3. 执行效果
bash
# 控制台输出示例
[执行中] 图片1 图片2 图片3
[完成] 图片1 (成功)
[开始] 图片4 (从队列补位)
[失败] 图片2 (超时)
[开始] 图片5 (继续补位)
...
最终统计:成功率: 12/15
关键优势:
- 流量平滑:始终维持≤3个并行上传
- 错误隔离:单个失败不影响整体流程
- 顺序保证:先进先出(FIFO)执行机制
性能优化:高级特性增强
1. 优先级队列 - VIP任务插队
javascript
enqueue(taskFunc, priority = 0) {
// ...(省略基础逻辑)
const taskWrapper = { run: wrappedTask, priority };
// 优先级插队逻辑
if (this.currentCount >= this.maxConcurrent) {
const index = this.pendingQueue.findIndex(
t => priority > t.priority
);
index === -1
? this.pendingQueue.push(taskWrapper)
: this.pendingQueue.splice(index, 0, taskWrapper);
}
}
// 使用示例 - 设置优先上传VIP商品图片
uploadQueue.enqueue(mockUpload('VIP'), 1); // 优先级高于默认0
2. 动态扩容 - 智能调整并发数
javascript
// 根据网络状况自动调整
setConcurrent(newMax) {
this.maxConcurrent = newMax;
// 扩容时立即触发等待任务
while (
this.currentCount < this.maxConcurrent &&
this.pendingQueue.length > 0
) {
const task = this.pendingQueue.shift();
task.run();
}
}
方案对比:三种进阶实现方式
实现方案 | 实现复杂度 | 调度精度 | 功能扩展性 | 适用场景 |
---|---|---|---|---|
基础Promise队列 | ★★☆ | ★★★ | ★★☆ | 简单并发控制 |
Priority Queue | ★★★ | ★★★ | ★★★ | 优先级任务系统 |
AsyncPool | ★★☆ | ★★☆ | ★★☆ | 固定池资源管理 |
选型黄金法则:
- 中小项目直接用基础Promise队列
- 复杂系统选择Priority Queue方案
- Node.js I/O密集型用AsyncPool
代码实现(完整版)
javascript
class SmartTaskQueue {
constructor(maxConcurrent = 5) {
this.maxConcurrent = maxConcurrent;
this.activeCount = 0;
this.queue = [];
}
enqueue(task, priority = 0) {
return new Promise((resolve, reject) => {
const taskWrapper = async () => {
this.activeCount++;
try {
const result = await task();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.activeCount--;
this._dequeue();
}
};
if (this.activeCount < this.maxConcurrent) {
taskWrapper();
} else {
this.queue.push({ task: taskWrapper, priority });
this.queue.sort((a, b) => b.priority - a.priority); // 优先级排序
}
});
}
_dequeue() {
if (this.queue.length === 0) return;
if (this.activeCount >= this.maxConcurrent) return;
const nextTask = this.queue.shift().task;
nextTask();
}
resize(newSize) {
this.maxConcurrent = newSize;
while (
this.activeCount < this.maxConcurrent &&
this.queue.length > 0
) {
this._dequeue();
}
}
}
何时使用任务队列?
- 网络请求:API调用、文件上传/下载
- 资源加载:图片懒加载、分块加载大文件
- 硬件交互:蓝牙/串口设备指令发送
- 批量操作:数据库写入、日志上传
实战技巧:配合Web Worker使用可避免阻塞主线程
javascript// 在Worker中使用任务队列 const worker = new Worker('task-worker.js'); worker.postMessage({ type: 'INIT_QUEUE', maxConcurrent: 4 });