これは、コピーアンドペーストに適した、完全Promise.all()
/map()
代替機能を備えた、同時実行制限のあるES7ソリューションです。
これと同様に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()
がもちろん最速ですが、他のバニラは実際の混雑シナリオでより望ましいパフォーマンスを発揮する可能性があります。
更新
他の人がすでに提案している非同期プールライブラリは、ほぼ同じように機能し、Promise.race()の巧妙な使用法でより簡潔な実装を備えているため、おそらく私の実装のより良い代替手段です:https://github.com/rxaviers/ async-pool / blob / master / lib / es7.js
うまくいけば、私の答えはまだ教育的価値を提供することができます。