A Promise to Escape the realm of Callback Hell

Let’s begin with a story.

You are creating an image gallery dedicated to steak lovers around the world. As a way of adding a little spice to the presentation, you decide to allow five steaks to fade onto the screen, one after another. In order to achieve this, you’ll want to make sure that the images are successfully loaded before you attempt to animate each one.

No problem right? You stretch out your hands, do a couple fist pumps, and proceed to unleash the fury and power of the Javascript callback unto the unsuspecting editor.

function imageCallBack(url, callback) {
  let img = new Image();
  
  img.onload = function() {
    callback(null, img);
  }

  img.onerror = function() {
    callback(new Error('Image was not loaded'));
  }

  img.src = url;
}

Let’s see how this will look when you try to chain a series of nested callback functions. We assume that a function named “animateImage()” will be called after the image has been successfully loaded.

imageCallBack('img/steak1.png', (err, steak1) => {
  if err throw err;
  animateImage(steak1, 'fadein');
  imageCallBack('img/steak2.png', (err, steak2) => {
    if err throw err;
    animateImage(steak2, 'fadein');
    imageCallBack('img/steak3.png', (err, steak3) => {
      if err throw err;
      animateImage(steak3, 'fadein');
      imageCallBack('img/steak4.png, (err, steak4) => {
        if err throw err;
        animateImage(steak4, 'fadein');
      });
    });
  });
});

Something seems wrong here. The code is starting to look real ugly.

Imagine if you had to debug five, six, seven, or even ten nested callbacks, each with its own layer of complexity. That would be absolutely terrifying.

Welcome to “callback hell”.

Looking at the code above, there are tons of repetition in error handling and way too many brackets. Is there a better way to handle such a request?

Thankfully, ES6 has given us the power of Promises, which allow us to rid the callback nesting that can occur when multiple asynchronous requests need to be made that also happen to depend on the outcome of each other.

Here’s the original callback function refactored and utilizing Promises.

function imagePromise(url) {
  return new Promise((resolve, reject) => {
    let img = new Image();

    img.onload = function() {
      resolve(img);
    }

    img.onerror = function() {
      reject(new Error('Image was not loaded'));
    }

    img.src = url;
  });
}

We see that the function is now returning a new Promise object. Its constructor has two arguments: resolve and reject. As you might have guessed, a resolved Promise returns the unravelled value while a rejected Promise will return an error.

Once you have a reference to a Promise object, you can call the “.then()” method to carry out an action if the Promise has been resolved.

The resulting code is much easier to read, understand, and debug.

Promise.all([
  imagePromise('steak1'),
  imagePromise('steak2'),
  imagePromise('steak3'),
  imagePromise('steak4'),
  imagePromise('steak5')
])
  .then(steaks => {
    steaks.forEach(steak => animateImage(steak, 'fadein'));
})
  .catch(err => {
    console.error(err);
});

This is the power of Promises in Javascript. They accomplish the same things as normal callbacks, but possess a nicer syntax and the ability to be chained in various ways.

Does this mean that all callbacks should be replaced with Promises? Probably not.

There are times when utilizing a callback is necessary because the callback needs to be run synchronously and more than once. Think of Javascript’s “forEach()” Array method and how it might be built by utilizing a callback for every element in the array. In that particular scenario, a Promise would not be able to achieve what the callback is capable of doing.

As a general rule of thumb, it is best if you use the right tool for the job. Promises shine when you need to make asynchronous requests that depend on multiple other asynchronous requests. If you ever find yourself chaining endless callbacks back to back, you may need to give your eyes a little rest and break out the Promises!

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s