Promises are one of the best things that happened to JavaScript in the past few years.

When we invoke a function that returns a promise, we chain the then() method of the promise to run a function when the promise resolves.

Here’s an example using the Fetch API:

fetch('/data.json')
  .then(response => { 
    console.log(response.status)
  })

What if there is an error during the fetch() call? Perhaps the network is unavailable. Or the network request returns an error.

The promise will reject. A promise will look something like this:

const thePromise = new Promise((resolve, reject) => {

})

Inside the promise we are passed 2 parameters, 2 functions. Inside the body, if all goes find, the resolve() function is called:

const thePromise = new Promise((resolve, reject) => {
  resolve('ok') //you can pass any value
})

If something bad happens, the reject() function is called:

const thePromise = new Promise((resolve, reject) => {
  reject('error message') //you can pass any value
})

If something goes bad, we must handle the promise rejection. We do so using the catch() method of the promise:

thePromise
  .catch(error => {
    console.error(error)
  })

We must always add a catch(), otherwise promises will silently fail.

We can chain catch() to a then() method:

thePromise
  .then(response => { 
    console.log(response)
  })
  .catch(error => {
    console.error(error)
  })

Or even multiple ones, if you have a chain of promises:

const thePromise = new Promise((resolve, reject) => {
  resolve({
    doSomething: function() {
      return new Promise((resolve, reject) => {
        reject('error!') //you can pass any value
      })
    }
  })
})

thePromise
  .then(response => { 
    return response.doSomething()
  })
  .then(response => { 
    console.log(response)
  })
  .catch(error => {
    console.log(error)
  })

In this case, if thePromise is rejected, the execution jumps directly to the catch() method.

You can add the catch() method in the middle of two then() methods, but you will not be able to break the chain when something bad happens. And the return value of catch() (which will have the undefined value if not specified) will be passed to the following then().

It’s best, in my opinion, to leave catch() at the end of the chain, and use it to handle all possible errors.

Error handling in my opinion is best in async/await, but sometimes we can’t avoid using promises, so that’s how you can do it.