Appearance
2636. Promise 对象池
ts
type F = () => Promise<any>
async function promisePool(functions: F[], n: number): Promise<any[]> {
// 利用数组自身的迭代器,减少变量的维护
const iterator = functions[Symbol.iterator]()
// 任务执行器,存在空闲,主动任务队列拿取新任务执行
async function worker() {
while (true) {
const { value: fn, done } = iterator.next()
if (done) break
await fn()
}
}
// 创建 n 个 worker,每个 worker 会连续取任务执行
const workers = Array.from({ length: n }, () => worker())
// 等待所有 worker 完成
return Promise.allSettled(workers)
}
/**
* const sleep = (t) => new Promise(res => setTimeout(res, t));
* promisePool([() => sleep(500), () => sleep(400)], 1)
* .then(console.log) // After 900ms
*/设计思路
这里本质上是一个并发控制问题。核心约束是:最多 N 个任务同时执行,但不能直接把所有任务一次性用 Promise.all 扔出去。
实现中用了两个关键设计:
1. 迭代器驱动消费。 functions[Symbol.iterator]() 返回一个迭代器,所有 worker 共享它。由于 iterator.next() 是有状态的(每次调用推进位置),多个 worker 不会拿到同一个任务。
2. worker 模式。 每个 worker 是一个 async function,它不停地从迭代器取任务执行,直到迭代器枯竭。N 个 worker 并发运行,保证同时最多 N 个任务在执行;一个 worker 完成当前任务后,就立即去取下一个。
Promise.allSettled vs Promise.all
这里用 allSettled 而不是 all,原因是:如果某个任务 reject,Promise.all 会立即短路,其他 worker 仍在执行中的结果就拿不到了。allSettled 则会等所有 worker 都结束。
复杂度
调度本身是 O(F),F 为任务数。真正的时间消耗在于任务自身。空间方面,迭代器是 O(1) 的额外开销——没有预分配结果数组,没有中间队列。
