题目
实现一个支持并发限制的异步请求队列
信息
- 类型:问答
- 难度:⭐⭐
考点
Promise, async/await, 并发控制, ES6+异步编程
快速回答
实现一个异步请求队列函数 asyncQueue(requests, limit),要求:
- 接收请求数组和并发限制数
- 使用 Promise 控制并发
- 所有请求完成后返回结果数组
- 保持结果顺序与输入一致
核心实现步骤:
- 使用
Array.map创建待执行队列 - 通过递归函数控制并发数量
- 使用
Promise.race动态管理执行池 - 利用
async/await处理异步流程
问题背景
在前端开发中,经常需要处理大量异步请求(如API调用)。浏览器对并发请求数有限制(通常6-8个),无限制并发会导致:
- 网络资源竞争导致部分请求阻塞
- 服务器压力过大可能拒绝服务
- 客户端性能下降
解决方案
async function asyncQueue(requests, limit) {
const results = [];
const executing = [];
for (const [i, request] of requests.entries()) {
const p = Promise.resolve().then(() => request());
results[i] = p;
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
return Promise.all(results);
}原理说明
- Promise.race 动态控制:通过不断检查执行池,确保运行中的请求不超过限制
- 结果顺序保持:提前在结果数组对应位置存储Promise,与输入顺序一致
- 执行池管理:每个请求完成后自动从执行池移除自身
使用示例
// 模拟异步请求
const mockRequest = (id, delay) => () =>
new Promise(resolve =>
setTimeout(() => resolve(`请求${id}完成`), delay)
);
// 创建10个请求
const requests = [];
for (let i = 1; i <= 10; i++) {
requests.push(mockRequest(i, Math.random() * 2000));
}
// 并发限制为3
asyncQueue(requests, 3)
.then(results => console.log(results));
// 输出:["请求1完成", "请求2完成", ...](保持原始顺序)最佳实践
- 错误处理:在请求函数内添加
.catch保证单个失败不影响整体 - 性能优化:根据实际网络环境动态调整并发数
- 取消机制:可结合
AbortController实现请求中断
常见错误
| 错误类型 | 示例 | 解决方案 |
|---|---|---|
| 结果顺序错乱 | 直接使用 Promise.all | 提前固定结果数组索引 |
| 并发控制失效 | 使用 for...of 无等待 | 正确使用 await Promise.race |
| 内存泄漏 | 未清理执行池引用 | 请求完成后立即移除 |
扩展知识
- Promise 组合器:
Promise.allSettled可获取所有请求状态(包含失败) - 高级模式:令牌桶算法、漏桶算法等更精细的流量控制
- 实际应用:图片懒加载队列、大文件分片上传、批量数据提交