Context: I need to make a large number of asynchronous calls (think around 300 to 3000 ajax calls) that are parallelizable. However, I do not want to strain the browser or server by calling them all at once. I also didn't want to run them sequentially because of the long time it would take to finish. I settled on running five or so at a time and derived this function to do so:
async function asyncLoop(asyncFns, concurrent = 5) {
// queue up simultaneous calls
let queue = [];
for (let fn of asyncFns) {
// fire the async function and add its promise to the queue
queue.push(fn());
// if max concurrent, wait for the oldest one to finish
if (queue.length >= concurrent) {
await queue.shift();
}
}
// wait for the rest of the calls to finish
await Promise.all(queue);
};
Where asyncFns is an iterable of (not yet called) asynchronous functions.
Problem: This works, however I found that it's not always true that oldest is the first to be complete. I wanted to modify the function so that it uses Promise.race to wait until the first promise succeeds, then continue from there. Yet, I don't know which promise to remove:
// if max concurrent, wait for the first one to finish
if (queue.length >= concurrent) {
await Promise.race(queue);
// ??? get race's completed promise
// queue.splice(queue.indexOf(completed), 1);
}
I could splice it out of the queue (which is now more of a set I guess) if I just knew the index of which one completed. It doesn't look like I can get the original promise from the derived one that race returns. Suggestions?
const [ idx, result ] = await Promise.race(promisesArr.map((promise, idx) => promise.then((result) => [ idx, result ]);
This won't cover exceptions though. To complete it, I have a special function handy (safelyExecuteAsync
) which returns a promise of a tuple [ error, result ]. With it, the code becomes:const [ idx, [error, result] ] = await Promise.race(promisesArr.map((promise, idx) => safelyExecuteAsync(promise).then((tuple) => [ idx, tuple ]);
– Bozcaada