Promises Error Handling

Introduction

One of the biggest concerns of complex applications, be they for web or desktop, is to handle errors effectively.

This requires the use of the conventional try..catch statements bundled with the throw keyword; in addition to listening for error events, laying out numerous if checks in the code and much more on this way.

Sometimes it also requires passing in callbacks to be fired on the occurrence of any errors. When this is the case and the overall code is quite involved, then using a callback simply means to be writing code that will ultimately lead to unmanageable clutter - as discussed in detail in the Promises Introduction chapter.

What's rather a better alternative is to use a promise!

In this chapter we shall introduce you to the catch() method inherited by all promises and see how it's analogous to the second argument to then(), and how to use all these features to handle bugs occurring in the wrapped-up asynchronous operation.

Let's begin!

What is error handling?

We'll start by answering the question - what is error handling - using our all-time favourite example i.e AJAX.

Suppose you make a request to some random file on your server using XMLHttpRequest() - the core of AJAX. What errors do you think can possibly occur in the whole request-response cycle?

Do this as a quick exercise - it will give you a good warmup on the topic!

To name a few:

  1. The request can be made to a non-existent file in which case the server would send a 404, Not Found, status code.

  2. The server script at the backend might have an invalid syntax in which case it would response with a 500 Internal Server Error, status.

  3. The client's network could be down in which case the error event would be dispatched.

  4. The request can violate the CORS policy in which case, once again, the error event will be fired.

and so on....

Actually, it depends on the application itself - for example it can be configured to parse a JSON response and then read a property on the parsed object to determine the status of the response.

Whatever the case be, the main point over here is that errors can happen in any asynchronous operation and thereby it's imperative for us to write code that handles them effectively.

How to handle errors? Well it's pretty elementary!

Error handling in JavaScript is usually done with two things: events and conditional statements.

Events such as error and abort frequently fire, even in simplistic applications; likewise it's common to provide onerror and onabort handlers to eventually respond to each case.

Similarly, often times one needs to lay out conditional checks in order target errors - for example by checking the status property of an XMLHttpRequest() object against the value 200 in the load event, we can throw an exception once we know it's not equal to 200.

As another example: we can check for the availability of the XMLHttpRequest() API and consequently throw an exception if it's not suported.

So now that we know how to handle errors in programming, it's finally time that we dive right into implementing it in promises.

Home > Courses > Advanced JavaScript > Promises - Error Handling

Promises Error Handling

What will you learn in this page?

  1. What is error handling

  2. How to handle errors in JavaScript

  3. The onRejected() callback

  4. The catch() method

PromisesChaining QuizBuffersBasics

Introduction

One of the biggest concerns of complex applications, be they for web or desktop, is to handle errors effectively.

This requires the use of the conventional try..catch statements bundled with the throw keyword; in addition to listening for error events, laying out numerous if checks in the code and much more on this way.

Sometimes it also requires passing in callbacks to be fired on the occurrence of any errors. When this is the case and the overall code is quite involved, then using a callback simply means to be writing code that will ultimately lead to unmanageable clutter - as discussed in detail in the Promises Introduction chapter.

What's rather a better alternative is to use a promise!

In this chapter we shall introduce you to the catch() method inherited by all promises and see how it's analogous to the second argument to then(), and how to use all these features to handle bugs occurring in the wrapped-up asynchronous operation.

Let's begin!

What is error handling?

We'll start by answering the question - what is error handling - using our all-time favourite example i.e AJAX.

Suppose you make a request to some random file on your server using XMLHttpRequest() - the core of AJAX. What errors do you think can possibly occur in the whole request-response cycle?

Do this as a quick exercise - it will give you a good warmup on the topic!

To name a few:

  1. The request can be made to a non-existent file in which case the server would send a 404, Not Found, status code.

  2. The server script at the backend might have an invalid syntax in which case it would response with a 500 Internal Server Error, status.

  3. The client's network could be down in which case the error event would be dispatched.

  4. The request can violate the CORS policy in which case, once again, the error event will be fired.

and so on....

Actually, it depends on the application itself - for example it can be configured to parse a JSON response and then read a property on the parsed object to determine the status of the response.

Whatever the case be, the main point over here is that errors can happen in any asynchronous operation and thereby it's imperative for us to write code that handles them effectively.

How to handle errors? Well it's pretty elementary!

Error handling in JavaScript is usually done with two things: events and conditional statements.

Events such as error and abort frequently fire, even in simplistic applications; likewise it's common to provide onerror and onabort handlers to eventually respond to each case.

Similarly, often times one needs to lay out conditional checks in order target errors - for example by checking the status property of an XMLHttpRequest() object against the value 200 in the load event, we can throw an exception once we know it's not equal to 200.

As another example: we can check for the availability of the XMLHttpRequest() API and consequently throw an exception if it's not suported.

So now that we know how to handle errors in programming, it's finally time that we dive right into implementing it in promises.

Rejection callback

Recall the then() method and the callback arguments we provide to it: the first one is the onFulfilled callback which fires once the promise is fulfilled while the second one is the onRejected callback which fires once the promise is rejected.

If onRejected is provided; well and good, but if it's not, then the default "Thrower" argument is taken to be the callback.

The question is that when does a given promise get rejected!

Well there are two ways to reject a promise: one is by invoking the reject() callback passed to the executor whereas the other is by throwing an exception explicitly inside the executor, using the throw keyword.

The way the latter works is described as follows:

When the Promise() constructor is instantiated, it immediately executes the provided executor function, inside a try block - much like the code below:

function Promise(executor) {
    // invoke the executor function
    try { executor(); }
    catch(e) { reject(e); }
}

The corresponding catch block calls the same reject() function passed to the executor, and supplies it with the error argument e, as shown above.

This means that any thrown exceptions inside the executor will cause the corresponding promise to be rejected with the thrown value.

Let's consider a couple of examples.

Following is the code to illustrate promise rejection, done by explicitly calling the reject() argument:

var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        reject("Sorry");
    }, 3000);
});

Here if we log the promise object p after 3 seconds, we'll get something similar to the following:

Promise {<rejected>: "Sorry"}

If we want we can also pass in a callback to then() to handle the promise's rejection after 3 seconds. This is shown below:

p.then(null, function(error) {
    console.log("An error occurred: " + error);
});

The second argument here is an anonymous function that logs an error message in the console, when invoked roughly after 3 seconds.

An error occurred: SorryRecall that it's the second argument to then() that deals with errors; NOT the first one, which in this case is set to null (it isn't required for the example and so there's no point of giving one).

Now for the second case - throwing an exception explicitly within the promise - consider the code below:

var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        throw "Sorry";
    }, 3000);
});

Although it's different syntactically, this code does exactly the same thing as the one shown above with reject(). When the statement throw "Sorry" is executed, control flow shifts to the internal catch block, which then calls the reject() function with the throw value "Sorry".

Once again, we can attach a failure callback to the promise p here, which'll operate exactly the same as in the previous example.

p.then(null, function(error) {
    console.log("An error occurred: " + error);
});

An error occurred: Sorry

Moving on, as we know from the previous chapter on Promise Chaining, then() returns a promise which depends on the respective passed-in callback to complete.

In the occasion where it throws an error itself, the returned promise is rejected with the thrown value.

Consider the code below:

var p = new Promise(function(resolve, reject) {
    resolve("OK");
});

var p2 = p.then(function(data) {
    throw "Sorry";
});

console.log(p2);

Promise {<rejected>: "Sorry"}

See how the main promise p is resolved but the one derived by calling then() i.e p2, is rejected - simply because the callback throws an error.

A derived promise is rejected with value v, if an exception v is thrown in the corresponding then()'s callback.

On the same lines, we can also return a promise in the callback that gets rejected eventually and thus causes the derived promise to get rejected as well:

var p = new Promise(function(resolve, reject) {
    resolve("OK");
});

var p2 = p.then(function(data) {
    // return a rejected promise
    return new Promise(function(resolve, reject) {
        reject("Sorry");
    });
});

If we log p2 after a while over here, we'll get something similar to the following:

Promise {<rejected>: "Sorry"}

This happens because when a then() callback returns a promise itself, the corresponding derived promise mimics that returned promise - in this case p2 mimics the promise in line 7.

Now retreating back to the scenario where an explicit exception is thrown inside the callback for then(), we have an amazing concept following from this very basic idea, which we're about to explore next.

First of all it's vital for us to understand that if we don't provide a failure callback to then(), the method will default it to the "Thrower" function - which will simply throw an exception with the value of the promise.

Consider the code below:

var p = new Promise(function(resolve, reject) {
    reject("Oops!");
});

p.then(null, function(error) {
    throw error;
});

This is the same as writing the following (with the second argument to then() omitted this time):

var p = new Promise(function(resolve, reject) {
    reject("Oops!");
});

p.then(null);

For more info on the default arguments to then(), please read Promises Basics.

With this understood, try to solve the task below and see how well have you learnt promises overall!

Last updated