这是我的ES7解决方案,适用于复制粘贴友好且功能完整Promise.all()
/map()
替代,并发限制。
类似于Promise.all()
它维护退货顺序以及非承诺退货值的回退。
我还对不同实现进行了比较,因为它说明了其他一些解决方案遗漏的某些方面。
用法
const asyncFn = delay => new Promise(resolve => setTimeout(() => resolve(), delay));
const args = [30, 20, 15, 10];
await asyncPool(args, arg => asyncFn(arg), 4);
实作
async function asyncBatch(args, fn, limit = 8) {
args = [...args];
const outs = [];
while (args.length) {
const batch = args.splice(0, limit);
const out = await Promise.all(batch.map(fn));
outs.push(...out);
}
return outs;
}
async function asyncPool(args, fn, limit = 8) {
return new Promise((resolve) => {
const argQueue = [...args].reverse();
let count = 0;
const outs = [];
const pollNext = () => {
if (argQueue.length === 0 && count === 0) {
resolve(outs);
} else {
while (count < limit && argQueue.length) {
const index = args.length - argQueue.length;
const arg = argQueue.pop();
count += 1;
const out = fn(arg);
const processOut = (out, index) => {
outs[index] = out;
count -= 1;
pollNext();
};
if (typeof out === 'object' && out.then) {
out.then(out => processOut(out, index));
} else {
processOut(out, index);
}
}
}
};
pollNext();
});
}
比较方式
const asyncFn = delay => new Promise(resolve => setTimeout(() => {
console.log(delay);
resolve(delay);
}, delay));
const args = [30, 20, 15, 10];
const out1 = await Promise.all(args.map(arg => asyncFn(arg)));
const out2 = await asyncPool(args, arg => asyncFn(arg), 2);
const out3 = await asyncBatch(args, arg => asyncFn(arg), 2);
console.log(out1, out2, out3);
结论
asyncPool()
应该是最好的解决方案,因为它允许新请求在任何先前请求完成后立即开始。
asyncBatch()
包含在内是作为比较,因为它的实现较容易理解,但是它的性能应较慢,因为需要完成同一批处理中的所有请求才能启动下一个批处理。
在这个人为的例子中,无限制的香草Promise.all()
当然是最快的,而其他香草在现实的拥挤场景中可能表现得更理想。
更新资料
其他人已经建议的async-pool库可能是我的实现的更好替代方案,因为它几乎相同地工作,并且通过巧妙地使用Promise.race()具有更简洁的实现:https : //github.com/rxaviers/异步池/blob/master/lib/es7.js
希望我的回答仍然可以提供教育价值。