在JavaScript中,由于其单线程的特性,传统的并发任务处理通常依赖于异步编程模式,如回调函数、Promises、async/await等。然而,当我们需要控制多个并发任务时,比如限制同时运行的任务数量,我们可以使用Promise结合一些额外的逻辑来实现。这里,我将介绍如何使用Promise
和Array.prototype.map
结合一个简单的并发控制函数来限制同时运行的任务数量。
场景描述
假设我们有一个API调用列表,我们需要并发地向这些API发送请求,但出于性能或API速率限制的考虑,我们希望同时进行的请求数量不超过某个特定值(比如5个)。
解决方案
我们可以创建一个函数concurrentRequests
,它接受三个参数:一个请求函数(这个函数返回一个Promise),一个请求参数数组,以及一个并发限制数。
function concurrentRequests(fn, args, concurrency) {
let activeCount = 0;
const results = [];
const queue = [...args];
// 辅助函数,用于执行下一个请求
function dequeue() {
if (activeCount >= concurrency || queue.length === 0) return;
activeCount++;
// 从队列中取出一个请求参数
const arg = queue.shift();
// 执行请求,并将结果添加到结果数组中
fn(arg).then(result => {
results.push(result);
activeCount--;
dequeue(); // 递归调用以处理下一个请求
}).catch(error => {
console.error(`Error processing ${
arg}:`, error);
activeCount--;
dequeue(); // 即使出错也继续处理下一个请求
});
}
// 开始处理队列
queue.forEach(() => dequeue());
// 返回一个Promise,该Promise在所有请求完成后解决
return new Promise(resolve => {
if (queue.length === 0) {
resolve(results);
} else {
// 当没有更多活动请求时,解析结果
const interval = setInterval(() => {
if (activeCount === 0 && queue.length === 0) {
clearInterval(interval);
resolve(results);
}
}, 100);
}
});
}
// 示例:使用fetch API模拟并发请求
function fetchData(url) {
return fetch(url).then(response => response.json());
}
// 假设我们有一系列URL需要并发请求
const urls = [
'https://api.example.com/data1',
'https://api.example.com/data2',
// ...更多URL
'https://api.example.com/dataN'
];
// 并发限制为5
concurrentRequests(fetchData, urls, 5).then(results => {
console.log('All data fetched:', results);
}).catch(error => {
console.error('Failed to fetch all data:', error);
});
解释
- 并发控制:通过
activeCount
变量跟踪当前活动的请求数量,确保不超过设定的并发限制。 - 队列处理:使用
queue
数组来存储待处理的请求参数,并通过dequeue
函数逐个处理它们。 - 递归调用:在请求成功或失败后,通过递归调用
dequeue
来处理下一个请求,以保持并发性。 - 结果收集:所有请求的结果都被收集到
results
数组中。 - 完成检测:使用
setInterval
来定期检查是否所有请求都已处理完毕,并在完成时解析返回的Promise。
结论
通过这种方法,我们可以有效地控制JavaScript中的并发任务数量,特别是在处理大量异步操作时非常有用。这种方法不仅适用于网络请求,还可以扩展到任何需要并发控制但受限于单线程环境的场景。