编码母题 / P0 / 异步编程 / 01
有序返回的并发任务池
先读清题意与边界,独立尝试,再展开思路并对照可运行的参考代码。
题目是什么
输入尚未启动的任务函数数组和并发上限。任务按输入顺序领取,运行数不超上限,最终结果仍按输入顺序排列;任一失败后停止启动新任务,已运行任务无须强制取消。
并发上限为 2,三个任务完成顺序即使是 B、A、C,结果仍须是 [A,B,C]。
练习起手式与验证
复制代码后补全空方法,再运行下方三组验证;复杂题所需的辅助类型与输入准备一并放在代码块中。
/*
* 题目:有序返回的并发任务池
* 要求:输入尚未启动的任务函数数组和并发上限。任务按输入顺序领取,运行数不超上限,
* 最终结果仍按输入顺序排列;任一失败后停止启动新任务,已运行任务无须强制取消。
* 示例:并发上限为 2,三个任务完成顺序即使是 B、A、C,结果仍须是 [A,B,C]。
*/
async function asyncPool(tasks, concurrency) {}
const sleep = (milliseconds) =>
new Promise((resolve) => setTimeout(resolve, milliseconds));
(async () => {
console.log(await asyncPool([], 2)); // 预期:[]
console.log(await asyncPool([() => 1, () => 2, () => 3], 2)); // 预期:[1, 2, 3]
const completionOrder = [];
const orderedResult = await asyncPool(
[
async () => {
await sleep(30);
completionOrder.push("A");
return "A";
},
async () => {
await sleep(10);
completionOrder.push("B");
return "B";
},
async () => {
await sleep(20);
completionOrder.push("C");
return "C";
},
],
2,
);
console.log(completionOrder); // 预期:["B", "A", "C"]
})().catch(console.error);解题思路
展开思路、不变量与常见边界
关键切入点:固定数量 Worker 共享递增索引,结果写回原索引;失败标记阻止后续领取。
四、核心模型与不变量 使用固定 Worker 模型:创建 min(concurrency, tasks.length) 个 Worker, 每个 Worker 完成当前任务后,再通过共享索引领取下一项。 必须始终成立: - 活动任务数不超过 Worker 数,也就不超过 concurrency。 - 共享索引只递增,每个任务最多领取一次。 - 结果写入原始索引,因此完成顺序不会改变结果顺序。 - 观察到失败后设置 stopped,其他 Worker 不再领取新任务。 五、复杂度 调度器处理每个任务一次,时间复杂度 O(n);结果数组占 O(n),Worker 占 O(min(n, concurrency))。任务自身耗时不计入算法复杂度。 六、常见错误 - 接收 Promise 数组,导致任务在调度前已经启动。 - 使用 results.push,错误地返回完成顺序。 - 使用 forEach(async () => {}),外层无法等待回调。 - reject 后继续领取任务,或误认为 reject 会取消活动任务。 - 用 splice(0, 1) 破坏输入并反复移动数组元素。 七、高价值变体 1. All-settled:记录每项成功或失败,单项失败不停止调度。 2. AbortSignal:停止新调度,并把取消信号传给支持协作取消的任务。 八、工程迁移 同一模型可用于批量请求、分片上传、文件处理、批量 Embedding、 Agent Tool 并行调用和 Eval 批处理。
参考代码与验证
“关键机制”注释用于解释实现,下载链接提供未经页面注释修改的原始 JS 文件。
// 关键机制:固定数量 Worker 共享递增索引,结果写回原索引;失败标记阻止后续领取。
async function asyncPool(tasks, concurrency) {
if (!Array.isArray(tasks)) {
throw new TypeError("tasks must be an array");
}
if (!Number.isInteger(concurrency) || concurrency < 1) {
throw new RangeError("concurrency must be an integer >= 1");
}
for (let index = 0; index < tasks.length; index += 1) {
if (typeof tasks[index] !== "function") {
throw new TypeError(`tasks[${index}] must be a function`);
}
}
if (tasks.length === 0) {
return [];
}
const results = new Array(tasks.length);
let nextIndex = 0;
let stopped = false;
async function worker() {
while (!stopped) {
const index = nextIndex;
if (index >= tasks.length) {
return;
}
nextIndex += 1;
try {
results[index] = await tasks[index]();
} catch (error) {
stopped = true;
throw error;
}
}
}
const workerCount = Math.min(concurrency, tasks.length);
const workers = Array.from({ length: workerCount }, () => worker());
await Promise.all(workers);
return results;
}
const sleep = (milliseconds) =>
new Promise((resolve) => setTimeout(resolve, milliseconds));
// 三组以上可直接运行的验证;异步题使用独立执行块。
(async () => {
console.log(await asyncPool([], 2)); // 预期:[]
console.log(await asyncPool([() => 1, () => 2, () => 3], 2)); // 预期:[1, 2, 3]
const completionOrder = [];
const orderedResult = await asyncPool(
[
async () => {
await sleep(30);
completionOrder.push("A");
return "A";
},
async () => {
await sleep(10);
completionOrder.push("B");
return "B";
},
async () => {
await sleep(20);
completionOrder.push("C");
return "C";
},
],
2,
);
console.log(completionOrder); // 预期:["B", "A", "C"]
console.log(orderedResult); // 预期:["A", "B", "C"]
let active = 0;
let maxActive = 0;
const concurrencyResult = await asyncPool(
Array.from({ length: 9 }, (_, index) => async () => {
active += 1;
maxActive = Math.max(maxActive, active);
await sleep(5);
active -= 1;
return index;
}),
3,
);
console.log(concurrencyResult); // 预期:[0, 1, 2, 3, 4, 5, 6, 7, 8]
console.log(maxActive); // 预期:3
const started = [];
const expectedError = new Error("failed");
console.log(await Promise.resolve(asyncPool(
[
async () => {
started.push(0);
await sleep(5);
throw expectedError;
},
async () => {
started.push(1);
await sleep(30);
return 1;
},
() => {
started.push(2);
return 2;
},
],
2,
)).then(() => false, () => true)); // 预期:true(拒绝)
await sleep(40);
console.log(started); // 预期:[0, 1]
let invalidInputStarted = false;
console.log(await Promise.resolve(asyncPool(
[
() => {
invalidInputStarted = true;
return 1;
},
null,
],
1,
)).then(() => false, () => true)); // 预期:true(拒绝)
console.log(invalidInputStarted); // 预期:false
console.log(await Promise.resolve(asyncPool([], 0)).then(() => false, () => true)); // 预期:true(拒绝)
})().catch(console.error);
复杂度分析
时间复杂度:O(n),不计任务本身。调度器对 n 个任务各领取和写回一次。
空间复杂度:O(n+c)。结果数组保存 n 项,至多 c 个 Worker 同时运行。