ES6 Promise Chaining

ES6 promises provide a native and more elegant way to handle asynchronous activity in JavaScript. Using promises, you can "chain" asynchronous activities together so that the return values from one activity can be used as the inputs for another. In this tutorial, we'll cover the basics of promise chaining including passing return values with then() and handling errors with catch().

What is a promise?

A promise represents the eventual completion of something. A promise either resolves or rejects based on the outcome of an operation. Here is a basic example:

let myPromise = (x) => {
  return new Promise((resolve, reject) => {
    if(x < 1){
      reject("failure")
    }else{
      resolve(x)
    }
  })
}

myPromise(1)
.then(data => {
  console.log(data)
})
.catch(e => {
  console.log(e)
})
//logs 1 to the console

In the above example, we define a function myPromise() that returns a Promise object. Notice that the Promise constructor takes two parameters (resolve, reject).

Based on the logic we implement in our myPromise() function, we either resolve or reject the promise. In this example, we reject the promise if the argument x is less than 1. Otherwise, we resolve the promise.

Using Then

We use then() with promises to handle the outcome of the promise. Notice how we pass the then() function a single function as a parameter. This function takes the resolved value data as an argument. In our example, we simply log the resolved value via console.log(data).

Using Catch

We use catch() to handle any Promise rejections. If our myPromise function is passed a value less than 1, the returned Promise is rejected and our catch() function logs the rejection (in this case "failure").

For more on the basics, see JavaScript ES6 Promises

Promise Chaining

Sometimes you want to use the output of one asynchronous activity as the input to another. For this to happen, you must ensure that the first asynchronous operation has completed before the next one starts. Otherwise, the second operation won't have the results of the first operation to work with!

This is where promise chaining comes into play. By "chaining" promises, you can wait for one asynchronous operation to finish before the next one starts:

let firstPromise = (x) => {
  return new Promise((resolve, reject) => {
    if(x < 1){
      reject("failure")
    }else{
      resolve(x)
    }
  })
}

let secondPromise = (x) => {
  return new Promise((resolve, reject) => {
    let result = x + 1
    if(result == 2){
      reject("failure")
    }else{
      resolve(result)
    }
  })
}

firstPromise(2)
.then(data => secondPromise(data))
.then(data =>{
  console.log(data)
})
.catch(e => {
  console.log(e)
})
//logs 3

In the above example, we have two functions firstPromise and secondPromise that return promises. We first call firstPromise(2). If the promise resolves, it passes the resolved value to the secondPromise() function. If the promise is rejected, the catch() block will execute and the secondPromise() function will never be called.

What would happen if we passed in 1 to firstPromise instead of 2? While the first promise would resolve, the second promise would be rejected since 1 + 1 = 2! In this case, our catch() block would handle the rejection of the second promise.

The above example demonstrates promise chaining. By chaining promises, you can more easily control the sequential execution of different asynchronous operations. Not only can you pass results from one operation to the next (via then()), you can also more elegantly catch errors and rejected promises with a single catch() block.

Conclusion

Promise chaining lets you control the flow of asynchronous operations in JavaScript. You can use the returned value of a promise as the inputs to another asynchronous operation using promise chaining. By using catch() you can conveniently implement a "catch all" for handling rejected promises and other errors.

Join the conversation...