[Promises 2/3] What is promise chaining and why is it important?

Harsh AnkurHarsh Ankur
6 min read

A neat and tidy code is almost as essential as the main logic behind an application. The bigger the app, the higher the importance of code tidiness.

Why? Because there are more people involved in building the app. And the software would require adding features, maintaining versions, and fixing bugs for a longer time with more people.

After some time has passed, developers often find it difficult to read/understand their own code let alone the code written by other people.

Code legibility has multiple facets ranging from proper commenting, variable naming, and indentations.

Look at this code below involving callbacks:

fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          console.log(filename + ' : ' + values)
          aspect = (values.width / values.height)
          widths.forEach(function (width, widthIndex) {
            height = Math.round(width / aspect)
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})
// Citation: http://callbackhell.com/

Handling asynchronous operations has always been tricky in Javascript. Callbacks are just another way where we pass functions as arguments to other functions which call the passed function only after processing their asynchronous requests. Hence, it takes care of waiting before calling the required function.

Promises also tend to get nest-y if not handled properly.

Now, look at this another example code involving promises:

driver.get('https://website.example/login')
  .then(function () {
    loginPage.login('company.admin', 'password')
      .then(function () {
        var employeePage = new EmployeePage(driver.getDriver());

        employeePage.clickAddEmployee()
          .then(function() {
            setTimeout(function() {
                var addEmployeeForm = new AddEmployeeForm(driver.getDriver());

                addEmployeeForm.insertUserName(employee.username)
                  .then(function() {
                    addEmployeeForm.insertFirstName(employee.firstName)
                      .then(function() {
                        addEmployeeForm.insertLastName(employee.lastName)
                          .then(function() {
                            addEmployeeForm.clickCreateEmployee()
                              .then(function() {
                                employeePage.searchEmployee(employee);
                            });
                        });
                    });
                });
            }, 750);
        });
    });
});
// Citation: https://stackoverflow.com/questions/35805603/are-nested-promises-normal-in-node-js

Thankfully, the above code can be written in this way too!

driver.get('https://website.example/login')
  .then(function() {
    return loginPage.login('company.admin', 'password')
  })
  .then(function() {
    var employeePage = new EmployeePage(driver.getDriver());
    return employeePage.clickAddEmployee();
  })
  .then(function() {
    setTimeout(function() {
      var addEmployeeForm = new AddEmployeeForm(driver.getDriver());

      addEmployeeForm.insertUserName(employee.username)
        .then(function() {
          return addEmployeeForm.insertFirstName(employee.firstName)
        })
        .then(function() {
          return addEmployeeForm.insertLastName(employee.lastName)
        })
        .then(function() {
          return addEmployeeForm.clickCreateEmployee()
        })
        .then(function() {
          return employeePage.searchEmployee(employee)
        });
    }, 750);
});
// Citation: https://stackoverflow.com/questions/35805603/are-nested-promises-normal-in-node-js

Did you notice how nested promises have now been arranged in nice chained and singularly aligned promises that involve a lot of 'return' statements that pass on data to the parent promise which handles it to the next .then().

This is called Promise Chaining!

The most important concept in Promise Chaining is returning a promise. It means that once you return a promise to where it was being called from, the parent gets to handle it irrespective of whether it succeeds or rejects.

We will learn about chaining promises from the below example. Let's combine the two codes that we wrote in my previous post related to promises. However, instead of adding 8 to a number only, we will add and then use that result to check if it is Sunday to add or subtract 8 again. Here is how it will look like.

let a = 32;
let samplePromise1 = new Promise((res1, rej1) =>
{
    setTimeout(() =>
    {
        a = a + 8;
        let samplePromise2 = new Promise(res2, rej2) =>
        {
            if (isTodaySunday())
            {
                a = a + 8;
                res2();
            }
            else
            {
                a = a - 8;
                rej2();
            }
        }
        samplePromise2
          .then(() => res1())
          .catch(() => rej1())
     }, 5000)
});

samplePromise1
  .then(() => console.log(`Success! Value of a is ${a}`))  // show a normal log on console.
  .catch(() => console.warn(`Warning! Value of a is ${a}`))   // Warn on console

What we did above is wrote samplePromise1 first and since we wanted to ensure that the checking of the day happens after "a" has been updated, we created the second promise inside our setTimeout function, and after we add 8 to "a". Now, we resolve the samplePromise2 inside of it with the condition day it is today, but we did not resolve our samplePromise1 yet. That we do after our samplePromise2 has been completed (whether resolved or rejected). Then we use that promise resolution to resolve samplePromise1.

But does it look very easy to understand at first look?

Let's rewrite it with promise chaining.

let a = 32;
let samplePromise1 = new Promise((res1, rej1) =>
{
    setTimeout(() =>
    {
        a = a + 8;
        res1();
     }, 5000)
});

let samplePromise2 = new Promise((res2, rej2) =>
{
  if (isTodaySunday())
  {
    a = a + 8;
    res2();
  }
  else
  {
     a = a - 8;
     rej2();
  }
}

samplePromise1
  .then(() => samplePromise2)  // samplePromise2 function is called only after samplePromise1 is resolved.
  .then(() => console.log(`Success! Value of a is ${a}`))  // show a normal log on console.
  .catch(() => console.warn(`Warning! Value of a is ${a}`))   // Warn on console

Did you notice how we could separate the entire function out of the nest and have them aligned to the start? We could also pass on a promise to another and handle all of the .then(s) together linearly. We do need to return the promise to the parent promise for the chaining to work.

Single line arrow functions have implicit return without using the "return" keyword

Our current example only showed us how we can ensure that a function has been processed completely before calling another function. However, this method of promise chaining can also be used to pass on values from one promise to another.

Let's look at how we can do that.

let samplePromise1 =
    (a) => new Promise((res1, rej1) =>
{
    setTimeout(() =>
    {
        a = a + 8;
        res1(a);
     }, 5000)
});

let samplePromise2 =
    (a) => new Promise((res2, rej2) =>
{
  if (isTodaySunday())
  {
    a = a + 8;
    res2(a);
  }
  else
  {
     a = a - 8;
     rej2(a);
  }
}

samplePromise1(32)
  .then(newA => samplePromise2(newA))  // pass response of samplePromise1 to samplePromise2
  .then(newResA => console.log(`Success! Value of a is ${newResA}`))  // show a normal log on console.
  .catch(newRejA => console.warn(`Warning! Value of a is ${newRejA}`))   // Warn on console

What we did above is converted samplePromise1 and samplePromise2 from simple promises to function expressions that accept a as arguments.

We could have written samplePromise1 example in the following way too.

let samplePromise1 =
    (a) => new Promise((res1, rej1) =>
{
    setTimeout(() =>
    {
        a = a + 8;
        res1(a);
     }, 5000)
});

// could be rewritten as following with return command as we have brace scoping now which lacks implicit return

let samplePromise1 = (a) =>
{
    return newPromise((res1, rej1) =>
    {
        setTimeout(() =>
        {
            a = a + 8;
            res1(a);
        }, 5000)
    });
}

// which could be written as

function samplePromise1(a)
{
    return newPromise((res1, rej1) =>
    {
        setTimeout(() =>
        {
            a = a + 8;
            res1(a);
        }, 5000)
    });
}

In essence, responses of both the functions samplePromise1 and samplePromise2 are promises. Therefore, calling these methods with appropriate arguments results in promises that we were calling before.

To summarize, we looked at dealing with sequential promises and passing values between them. We saw how we can chain .then and .catch of multiple promises into one thread.

Next, we will look into proper ways of dealing with parallel promises where we need to wait for either or all of the promises to process before we proceed further. We will also look at some real-life scenarios on how to club promises depending on conditions. We will finish our learning on Promises in the next post.

Thank You!

3
Subscribe to my newsletter

Read articles from Harsh Ankur directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Harsh Ankur
Harsh Ankur

I am a full stack developer with expertise in REST API development. I have experience in AWS services. I am passionate about Javascript but I love that we can make it type compatible with Typescript while retaining all the fun and ease of using Javascript.