What is an unhandled promise rejection?

November 09, 2019
Thomas Duffy

Written by Thomas Duffy

promise.all error handling

In Javascript, dealing with promises can be confusing and frustrating. One of the most common errors or warnings I see riddling test suites and codebases is the good old “Unhandled Promise Rejection.” I know I’ve struggled with understanding this in the past, so hopefully, this post can help you better understand promise rejections and getting rid of that pesky error once and for all.

Understanding the problem.

First, let’s understand what is causing this error. We’ll start by going through the different states of a promise.

An everyday use case for a promise would be handling the result coming back from a network call. Promises can have three states, pending, resolved, and rejected. In an attempt to explain the different states of a promise, I’m going to write a stripped-down example of the fetch API using the promise constructor.

const fetch = (url, param) => {
  return new Promise((resolve, reject) => {
    // stuff will go here.
  });
};

Whats a Pending Promise?

The first state is pending. The pending state of a promise represents that the result of the async operation is not ready. So let’s add the first step to our fetch method.

const fetch = (url, param) => {
  return new Promise((resolve, reject) => {
    // make network request (let's pretend that it can take 200ms - 1s) for the response to come back. The point is, we don't know when it's going to be done.
    const response = someMadeUpfunction(url, params);
  });
};

If you console the above method, you’ll see it in a pending state. From the pending state, the promise can be moved into a resolved or rejected state.

Whats a Resolved Promise?

If the fetch operation is successful and the data is ready, the promise will then invoke the resolve method with the result. Let’s take a look at how a promise resolves.

const fetch = (url, param) => {
  return new Promise((resolve, reject) => {
    const response = someMadeUpfunction(url, params); // { data: 'example-response' }
    if (response) {
      // moved into resolved state
      resolve(response);
    }
  });
};

When calling the resolved method, the response passed into it can now be accessed through the .then method on the promise. To access the response, you must first pass it a fulfillment handler/callback as an argument. A fulfillment handler is basically like giving the promise instructions of what to do with the response when it’s available. Below is an example of the usage of the .then method using our mock fetch API from above.

//Successful response
const data = fetch("http://example-api.com").then(response => {
  const data = response;
  // this is the response that was passed into the resolve method  above.
  console.log(data); // { data: 'example-response' }
});

So to recap, when a promise is successful, and in a resolved state, you need to register a fulfillment handler/callback to access the response.

Whats a Rejected Promise?

Next, let’s look at what happens if the fetch call fails for some reason.

const fetch = (url, param) => {
  return new Promise((resolve, reject) => {
    const response = someMadeUpfunction(url, params);
    if (response) {
      // moved into resolved state
      resolve(response);
    } else {
      // if response fails the reject method is called. Moving the promise into a rejected state.
      reject("error, response failed from fetch");
    }
  });
};

As you can see, if someMadeUpfunction fails, then the reject callback is called with the error as the argument. Similar to the resolve method, you can now access the error passed into the reject.

At this point, you might be asking yourself, “How do I access the error?” The answer is similar to when the promise is resolved; you access the error by registering a handler/callback by passing it to the .catch method on the promise. Below is an example of how you can access the error when a promise is rejected.

//Successful response
const data = fetch(URL).then((responseFrom) => {
     const data = responseFrom;
     // this is the response that was passed into the resolve method  above.
}).catch((err => {
   // do some stuff with the error passed in from reject
   const error = err;
   // expected response
   // error, response failed from fetch
});

To recap, To register a rejection handler in a promise chain, you need to call the .catch method on the promise and pass it to your rejection handler.

Finally, the Unhandled Promise Rejection error!

Now that we have an idea of the different states of a promise, can you think of why the “Unhandled Promise Rejection” error may be occurring? If you’re thinking, “Well, I need to tell the promise what to do if it rejects.” Then this article did its job! If not, then I’m sorry, I did my best, keep reading and ill explain.

If you do not provide a rejection handler callback to the promise, and it fails, this is where the Javascript runtime gets upset and throws the “Unhandled Promise Rejection” error. If you don’t tell the promise how to handle the error if it rejects, the error doesn’t know where to go.

The solution.

The solution to the elusive “Unhandled Promise Rejection” error is always to register a rejection handler/callback using the .catch method on the promise.

fetch
  .then(res => {
    // do something if a success.
  })
  .catch(error => {
    // do something if a reject.
  });

Conclusion

I hope this article doesn’t only help you get rid of that pesky error, but also help you understand why the error is occurring at a root level. To recap, I’ll summarize what to take away from this article. First, there are three states a promise can be in, pending, resolved, and rejected. Second, you have always to register a fulfillment handler using the .then method if the promise is resolved. Third, you must also designate a rejection handler/callback using the promises .catch method to give it instructions on what to do if the promise is rejected.

I hope this post helps someone struggling to understand why this error keeps showing up.


Thomas Duffy

Written by Thomas Duffy