JavaScript Promises

JavaScript Promises

Putting a stop to callback hell
Ferenc Almasi • šŸ”„ 2021 November 11 • šŸ“– 6 min read

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.

Looking to improve your skills? Check out our interactive course to master JavaScript from start to finish.
Master JavaScript

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);
callback.js
Copied to clipboard!

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!!! šŸŽ‰');
                            });
                        });
                    });
                }); 
            }); 
        });
    });
});
hell.js
Copied to clipboard!

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);
});
promise.js
Copied to clipboard!

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!"
promise.js
Copied to clipboard!

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);
});
promise.js
Copied to clipboard!

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);
});
promise.js
Copied to clipboard!

Keep in mind that in case you have a promise that rejects first, theĀ thenĀ handler will be called in that case too.

Looking to improve your skills? Check out our interactive course to master JavaScript from start to finish.
Master JavaScript

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);
})();
async.js
Copied to clipboard!

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!

Did you find this page helpful?
šŸ“š More Webtips
Frontend Course Dashboard
Master the Art of Frontend
  • check Unlimited access to hundred of tutorials
  • check Access to exclusive interactive lessons
  • check Remove ads to learn without distractions
Become a Pro

Recommended