Let’s say I have async functions calling external APIs with variable response times defined as below:
async function promise1() { /* API Call */ }
async function promise2() { /* API Call */ }
async function promise3() { /* API Call */ }
// ... up to promiseN
If I wanted to run them all in parallel and wait for all of them to resolve, I would run them like result = Promise.all([promise1, promise2, promise3,...,promiseN])
.
However, I have a slightly different requirement:
- I want to start all the promises in parallel.
- As soon as the first one resolves (whichever that is), I want to wait 500 milliseconds for the other promises to resolve.
- After that 500 ms window, I want to ignore any remaining unresolved promises.
result
should contain all the promises that resolved by the time the 500 ms window ends.
So for example, if promise2
resolves first at time T
, I want to wait until T + 500
ms for the others to resolve. Any promises that haven’t resolved by then should be ignored.
How can I accomplish this in JavaScript?
4
Instead of Promise.all
you will need Promise.any
(see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any) and chain a .then()
, incapsulating your 500ms wait into a second promise, which, when fulfilled, so 500ms passed after the first promise fulfilled, loop your promises and reject those which were not fulfilled yet.
3
You could await for the first promise to settle with Promise.race
. Then create a timeout promise (that rejects after 5 seconds), and let each of the promises race with that timeout promise. Await the results with Promise.allSettled
.
Here is a demo:
// Helper functions
const delay = (ms, value) => new Promise(resolve => setTimeout(() => resolve(value), ms));
const timeout = (ms, err) => new Promise((_, reject) => setTimeout(() => reject(err), ms));
// Create a list of asynchronous functions for the demo
// They fulfull after 2, 4, 6, 8, or 10 seconds respectively, and provide as value that number of seconds.
const asyncTasks = [2, 4, 6, 8, 10].map(i => () => delay(1000 * i, "value " + i));
async function main() {
// Create all the promises
let promises = asyncTasks.map(task => task());
console.log("Promises are created. Waiting for first one to settle...");
// Get first settlement
try {
await Promise.race(promises);
} catch (e) {} // Silent also when rejection
console.log("First promise settled. Start timer...");
const timer = timeout(5000, "timeout");
// Let each of the promises race with the timer:
promises = promises.map(p => Promise.race([p, timer]));
// Await all these race-promises to settle
const results = await Promise.allSettled(promises);
console.log("Here are the results:");
for (const result of results) console.log(JSON.stringify(result));
}
main();
This is my attempt to implement the logic that @lajos-arpad suggested. As he described I use the first resolved promise of Promise.any
to trigger a second promise the resolves after a 500ms timeout. After the delay I pack all of the resolved promises into an array and resolve the 2nd promise.
function d(delay, error = false) {
return new Promise((res, rej) => {
setTimeout(() => {
console.log("logged :: ", delay)
if (error) {
rej(new Error(`error ${delay}`))
return
}
res(`done ${delay}`)
}, delay);
});
}
async function go() {
const promises = [d(1000), d(1200), d(1400, true), d(1500), d(1600), d(5000)]
await Promise.any(promises);
const res = []
promises.forEach((p) => {
p.then(r => res.push({
status: "fulfilled",
value: r
})).catch(e => res.push({
status: "rejected",
reason: `${e}`
}))
})
const delayedResults = await new Promise(resolve => setTimeout(() => {
resolve([...res])
}, 500))
console.log("delayed results :: ", delayedResults)
}
go();
I have edited my answer per comments and added error handling. It now returns patterned on Promise.allSettled
4