🎄 Get 50% off from our JavaScript course for the holidays! 🎄
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.
JavaScript Course

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:

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.


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!

Share on
  • twitter
  • facebook
JavaScript Course Dashboard

Tired of looking for tutorials?

You are not alone. Webtips has more than 400 tutorials which would take roughly 75 hours to read.

Check out our interactive course to master JavaScript in less time.

Learn More

Recommended

🎉 Thank you for subscribing to our newsletter. x