All 4 Flavours of Promises in Javascript

Promise.all Promise.any Promise.allSettled Promise.race

Photo by Safar Safarov on Unsplash

We use promises in order to achieve to some asynchronous tasks. And this is how a promise looks like:

Suppose we have a list of promises. One the way to handle them is to execute them one by one, which serves the purpose but it defies the purpose of readability and some times creates a mess.

p1.then(callback).catch(errorCallback)
p1.then(callback).catch(errorCallback)
p1.then(callback).catch(errorCallback)
p1.then(callback).catch(errorCallback)
p1.then(callback).catch(errorCallback)
p1.then(callback).catch(errorCallback)

In order to run collection of promises in more cleaner way, we have 4 flavours of it which has their own use cases.

Flavour 1: Promise.all()

Promise.all() accepts an array of promises, and once all promises are resolved it returns an array containing all the data resolved by passed promises in order we have passed the promises.

function asyncTask(message, delay) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(message);
}, delay);
});
}

const task1 = asyncTask("Task 1 Done", 2000);
const task2 = asyncTask("Task 2 Done", 3000);
const task3 = asyncTask("Task 3 Done", 1000);
const task4 = asyncTask("Task 4 Done", 5000);

Promise.all([task1, task2, task3, task4]).then(function (results) {
console.log(results);
});

// results will contain:
Array(4) [ "Task 1 Done", "Task 2 Done", "Task 3 Done", "Task 4 Done" ]

Key takeaways about Promise.all()

  1. Time take to finish all promises is equal to the largest time consuming promise.
    In above passed tasks, task4 is taking most the time, hence total time taken by the Promise.all() is 5000 ms .
  2. If any promise in the passed list got rejected then the catch block is directly called, not matter if all other Promises got resolved or not.
function asyncTask(message, delay) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
if (message === "Task 3 Done") {
// Rejecting third task
reject("Error occured");
} else {
resolve(message);
}
}, delay);
});
}

const task1 = asyncTask("Task 1 Done", 2000);
const task2 = asyncTask("Task 2 Done", 3000);
const task3 = asyncTask("Task 3 Done", 1000);
const task4 = asyncTask("Task 4 Done", 5000);

Promise.all([task1, task2, task3, task4])
.then(function (results) {
// this will never be called
console.log(results);
})
.catch(function (error) {
console.log(error);
});

// In the console Error occured will be printed.

Flavour 2: Promise.allSettled()

Promise.allSettled() accepts an array of promises, and once all promises are either resolved or rejected then it returns an array containing all the data with resolved and rejected promises in order we have passed the promises.

function asyncTask(message, delay) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
if (message === "Task 3 Done") {
reject("Error occured");
} else {
resolve(message);
}
}, delay);
});
}

const task1 = asyncTask("Task 1 Done", 2000);
const task2 = asyncTask("Task 2 Done", 3000);
const task3 = asyncTask("Task 3 Done", 1000);
const task4 = asyncTask("Task 4 Done", 5000);

Promise.allSettled([task1, task2, task3, task4])
.then(function (results) {
console.log(results);
})

// console.log(results) output;
0: Object { status: "fulfilled", value: "Task 1 Done" }

1: Object { status: "fulfilled", value: "Task 2 Done" }

2: Object { status: "rejected", reason: "Error occured" }

3: Object { status: "fulfilled", value: "Task 4 Done" }

Key takeaways about Promise.allSettled()

  1. Unlike Promise.all(), it does not short circuit if any promise got rejected. It will keep executing other promises until all of the passed promises either got rejected or resolved.
  2. catch block is never get called in Promise.allSettled because all the data is returned in then block with the information of rejected and resolved promises.
function asyncTask(message, delay) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
reject("Error occured");
}, delay);
});
}

const task1 = asyncTask("Task 1 Done", 2000);

Promise.allSettled([task1])
.then(function (results) {
console.log(results);
// above line will log
// [{ status: "rejected", reason: "Error occured" }]
})
.catch(function (error) {
console.log(error); // It will never get called.
});

Flavour 3: Promise.race()

Here race literally means race among tasks, whichever promise got resolved or rejected first, Promise.race() will stop execution and will simply return the data of resolved or rejected promise.

Like Promise.all() and Promise.allSettled() it also accepts an array or promises.

function asyncTask(message, delay) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
if (message === "Task 3 Done") {
reject("Error occured");
} else {
resolve(message);
}
}, delay);
});
}

const task1 = asyncTask("Task 1 Done", 2000);
const task2 = asyncTask("Task 2 Done", 1000);
const task3 = asyncTask("Task 3 Done", 2000);
const task4 = asyncTask("Task 4 Done", 5000);

Promise.race([task1, task2, task3, task4])
.then(function (result) {
console.log(result); // Task 2 Done will be logged in the console
// Here Task2 has shortest time, so it will win the race.
// Here result will not be an array.
// result will be the data returned by resolve method
})

What if a rejected promise wins the race?

function asyncTask(message, delay) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
if (message === "Task 3 Done") {
reject("Error occured");
} else {
resolve(message);
}
}, delay);
});
}

const task1 = asyncTask("Task 1 Done", 2000);
const task2 = asyncTask("Task 2 Done", 1000);
const task3 = asyncTask("Task 3 Done", 200); // Shortest time 200ms
const task4 = asyncTask("Task 4 Done", 5000);

Promise.race([task1, task2, task3, task4])
.then(function (results) {
console.log(results);
})
.catch(function (error) {
console.log(error); // Task 3 has shortest time, and it wins the race.
// Error occured will be logged in the console.
});

Flavour 4: Promise.any()

This is somewhat similar to Promise.race . Here also race is happening.
But with good runners only, if any runner fall down during the race, the race won’t stop unlike Promise.all . The race will continue to happen until one of the runner reaches the finish line.

The only difference with Promise.all()is that if any promise got rejected, instead of directly short circuiting, Promise.any()waits for all other promise to get resolved.

Like all other flavours, it also accepts an array of promises.

function asyncTask(message, delay) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
if (message === "Task 3 Done") {
reject("Error occured");
} else {
resolve(message);
}
}, delay);
});
}

const task1 = asyncTask("Task 1 Done", 2000);
const task2 = asyncTask("Task 2 Done", 1000);
const task3 = asyncTask("Task 3 Done", 200); // Winner Tasks, but it's being rejected
const task4 = asyncTask("Task 4 Done", 5000);

Promise.any([task1, task2, task3, task4])
.then(function (results) {
console.log(results); // Task 2 Done will be logged in the console.
// Task 2 will win the race, because runner (Task3)
})

What if every runner (task) falls down during the race i.e. got rejected?

catch block is called, with a predefined error message.

function asyncTask(message, delay) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
reject("Error occured"); // Passed error message won't be available in catch block.
}, delay);
});
}

const task1 = asyncTask("Task 1 Done", 2000);
const task2 = asyncTask("Task 2 Done", 1000);
const task3 = asyncTask("Task 3 Done", 200);
const task4 = asyncTask("Task 4 Done", 5000);

Promise.any([task1, task2, task3, task4])
.then(function (results) {
console.log(results);
})
.catch(function (error) {
console.log("error", error);
// error: AggregateError: All promises were rejected
});

Key takeaways about Promise.any()

  1. Whatever we pass in reject message, won’t be available in catch block.
  2. If all promise got rejected, it will throw a predefined error
    AggregateError: All promises were rejected

That’s all about all variants of Promises. :)

Exercise: As per above info learned, let’s solve a simple output question.

// Can you Guess what will be printed in the console in case we have empty
// array passed inside all 4 variants of Promises?

Promise.all([])
.then(function (data) {
console.log("then block all", data);
})
.catch(function () {
console.log("then block allSettled");
});

Promise.allSettled([])
.then(function (data) {
console.log("then block allSettled", data);
})
.catch(function (error) {
console.log("then block allSettled");
});

Promise.any([])
.then(function () {
console.log("then block any");
})
.catch(function (error) {
console.log("catch block any", error);
});

Promise.race([])
.then(function () {
console.log("then block race");
})
.catch(function (error) {
console.log("catch block race");
});

.

.

.

.

.

.

Solution:

then block all,  Array [] => In case of Promise.all()

then block allSettled , Array [] => In case of Promise.setteled()

catch block any AggregateError: No Promise in Promise.any was resolved

For Race, nothing will be logged.

--

--