
JavaScript Promises
If you remember the world of callbacks youāve been part of the JavaScript ecosystem for some time now. With the introduction of ES6, Promises came along to bring a better world.
Before that, callbacks provided a way to handle asynchronous operations. They were a powerful way of executing code once an asynchronous operation has finished. But JavaScript evolves and now itās time to move on.
So we will look into Promises, which proved to be superior to callbacks. But first to avoid any confusion, letās define what exactly are callbacks.

Callbacks
So what are callbacks?
Callbacks are functions passed as an argument to other functions to be used once an event has occurred or a task is finished.
To give you the simplest example, think of an event listener listening for a click event.
// This function will only run when the event occurs
const onClick = () => console.log('āļø');
document.addEventListener('click', onClick);
The second argument you pass toĀ addEventListener
Ā is a callback.
Callback hell happens when you have to wait for multiple, consecutive events that depend on the result of each other. Imagine loading in images. You start wrapping one callback into another until you get a nice Christmas tree:
getImage('base', data0 => {
getImage(data0, data1 => {
getImage(data1, data2 => {
getImage(data2, data3 => {
getImage(data3, data4 => {
getImage(data4, data5 => {
getImage(data5, data6 => {
getImage(data6, data7 => {
console.log('All images have been retrieved!!! š');
});
});
});
});
});
});
});
});
So what is the cure to all this madness?
The Alternative
Of course, the alternative way which this article is about is the use of Promises. TheĀ Promise
Ā object is a Web API which is accessible through the globalĀ window
Ā object. Itās an asynchronous operation that can be used in place of a callback. TheĀ Promise
Ā object can always be in one of three states:
Initially, it has aĀ pending
Ā state, meaning the Promise hasnāt finished operation yet. When the Promise is completed, it enters into one of two states:
- Fulfilled: meaning that the operation was completed successfully
- Rejected: meaning that the operation failed because of an error
Advantages
There are a number of advantages when it comes to Promises over Callbacks. The most prominent is that they are composable, unlike callbacks. This is the reason you can avoid callback hell with it.
It also provides an API to wait for only one result from multiple concurrent pending promises. The fastest response will be fulfilledĀ (or rejected)Ā and takes the price. The others will be discarded. We will have a look at this later on.
Disadvantages
Of course, nothing comes for free. Just like everything else, Promises also have disadvantages. Probably the most common problem is that they are not available in older browsers. They have to be polyfilled. However, global adaptation is wide. As of writing this article, itās aroundĀ 94%.
Believe it or not, because of the way they work, they are also slower than traditional callbacks. And they can only operate on a single value at a time. Yet because of their composability, they are a better choice to callbacks.
So how can you use them in your code? Letās see some examples!
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Resolved!");
}, 1000);
});
To create a Promise, simply call it with theĀ new
Ā constructor, passing in aĀ resolve
Ā andĀ reject
Ā parameter, which can be used as functions. The above example will resolve the Promise after 1 second with the text āResolved!ā. To use the resolved value, you can call theĀ then
Ā method on it.
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Resolved!");
}, 1000);
});
promise.then(result => console.log(result)); // The value of result will be "Resolved!"
Now say you have multiple promises that you want to handle. You can just bake them inĀ Promise.all
Ā and call a single callback.
Promise.all
const promise1 = ...;
const promise2 = ...;
const promise3 = ...;
Promise.all([promise1, promise2, promise3]).then(results => {
// results will be an array containing all promises
console.log(results);
});
For example, you want to retrieve multiple users at once using theĀ fetch
Ā API. TheĀ fetch
Ā API also returns a promise so you can callĀ then
Ā on it.Ā Promise.all
Ā expects an array of promises. TheĀ then
Ā handler will be called whenever all three of the promises have either been resolved or rejected.
Promise.race
But what if you have multiple concurrent pending promises, but you only need to use the one that returns first? This is whatĀ Promise.race
Ā is for:
const promise1 = ...;
const promise2 = ...;
const promise3 = ...;
Promise.race([promise1, promise2, promise3]).then(result => {
// results will be the one that resolves or rejects the fastest.
console.log(result);
});
Keep in mind that in case you have a promise that rejects first, theĀ then
Ā handler will be called in that case too.

Further Enhancing Readability
To make things even simpler, we can useĀ async/await
Ā in conjunction with promises to write asynchronous code in a synchronous manner. To go with the previous examples, instead ofĀ Promise.all
, you could write:
(async = () => {
const result1 = await promise1;
const result2 = await promise2;
const result3 = await promise3;
// This console.log will only be called once all promises have been returned
console.log(result1, result2, result3);
})();
Here, every variable returns a promise. By using the keywordĀ await
Ā in front of them, we tell JavaScript to wait for the completion of the promise. Note that in order to use theĀ await
Ā keyword, you must be inside of anĀ async
Ā function.
And now that you know everything about promises and you have it in your tool belt, make sure youāll never fall into the trap of callback hell again. š¹
Thank you for reading through, happy coding!
Unlimited access to hundred of tutorials
Access to exclusive interactive lessons
Remove ads to learn without distractions