功能说明
mapWithConcurrency 把一个数组映射成异步结果,但同一时刻运行的任务数不会超过 limit。它适合批量请求、文件处理、调用外部接口等需要限制并发度的场景。结果按输入下标写入,因此任务完成的先后顺序不会改变返回数组的顺序。
默认采用快速失败策略:任意任务抛错后不再领取新任务,并在所有已启动的 worker 结束后抛出第一个错误。如果把 stopOnError 设为 false,函数会继续处理剩余元素,最终返回结果数组,调用方需要自行检查哪些位置可能没有值。传入 AbortSignal 可以在 worker 领取下一个任务前中止执行。
关键代码
启动的 worker 数量取 limit 与数组长度的较小值。每个 worker 循环领取递增下标,并在进入 mapper 前检查取消信号;异常被记录到 firstError,快速失败模式下直接退出循环。
const worker = async () => {
while (true) {
if (signal?.aborted) {
throw signal.reason ?? new DOMException("Aborted", "AbortError");
}
if (failed && stopOnError) return;
const index = nextIndex;
nextIndex += 1;
if (index >= items.length) return;
try {
results[index] = await mapper(items[index], index);
} catch (error) {
if (!failed) {
failed = true;
firstError = error;
}
if (stopOnError) return;
}
}
};
使用注意
取消信号只能阻止尚未开始的任务,已经进入 mapper 的异步操作必须自己监听 signal 并在内部停止,否则仍会消耗资源。快速失败模式下,Promise.all 会在第一个 worker 抛错时拒绝,其他 worker 可能仍在运行;如果希望等待全部结束,应使用 allSettled 或改为收集错误模式。
items 必须是真正的数组,类数组或可迭代对象需要先转换。limit 必须为正整数;若大于数组长度,实际 worker 数会降为数组长度。收集错误模式下结果数组可能包含未赋值的空洞,读取前要判断。mapper 抛出的值会原样抛出,不会被包装成新的错误类型。
验证方式
示例先以并发 2 处理 5 个睡眠时间递增的任务,输出 [1, 4, 9, 16, 25],并通过 order 数组观察 start 与 end 交错,确认同时运行数不超过 2。第二组任务在值为 2 时抛错,catch 应得到 failed on 2。把睡眠时间调大后运行,可以看到只有前两个任务开始,直到其中一个结束才会启动第三个。