Author avatar

Zachary Bennett

Access "this" in a Promise's Catch Block

Zachary Bennett

  • Oct 30, 2020
  • 5 Min read
  • 52 Views
  • Oct 30, 2020
  • 5 Min read
  • 52 Views
Web Development
Front End Web Development
Client-side Frameworks
React

Introduction

Promises were a revolutionary addition to the JavaScript programming language. Before promises, it was necessary to pass callbacks into function calls in order to model asynchronous control-flow. Promises give you a much more readable and maintainable way to manage asynchronous actions in your React app. The then and catch functions that a promise provides enable you to chain functions asynchronously and handle errors. However, using promises in your React components can be confusing when it comes to scope and the this keyword. In JavaScript, the this keyword allows you to access the context object of the surrounding lexical scope. In this guide, you will learn how to ensure that your promise catch blocks use the right context within your React components so that you can easily access component properties via this.

Let's dive in!

Binding Component Scope

First, the problem. When using promises in your React component, your first instinct may be to handle a promise like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ... MammalComponent.jsx

fetchMammals() {
    return fetch(this.mammalsUrl)
             .then(function(response) {
                return response.json();
             });
}

initMammalsState() {
    const mammalJsonPromise = this.fetchMammals();
    mammalJsonPromise
        .then(function(mammals) {
            this.setState({ mammals });
        })
        .catch(function(error) {
            this.error = error;
        });
}


// ...
jsx

At first glance, this code seems like it would work. It's simply using the Fetch API to grab some JSON and then handle the promise returned. It also sets the error property on the component itself in the event an error occurs in the catch block. But unfortunately, this code fails! If you create a component using the code above, you will likely see an error that says something akin to Can't find property <prop-name> of undefined. This is because your promise doesn't have access to the right this context!

The first way you can solve this issue is by using the bind function that is available on JavaScript's Function prototype. The bind function lets you inject a this context into the scope of the function you are working with. Below, you will find the functions above refactored in order to take advantage of this capability. Note how even the component functions themselves are bound to the React component's this context in the constructor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ... MammalComponent.jsx

constructor(props) {
    super(props);

    // Bind the 'this' context of the component to our class functions
    this.fetchMammals = this.fetchMammals.bind(this);
    this.initMammalsState = this.initMammalsState.bind(this);
}

fetchMammals() {
    return fetch(this.mammalsUrl)
             .then(function(response) {
                return response.json();
             });
}

initMammalsState() {
    const mammalJsonPromise = this.fetchMammals();
    mammalJsonPromise
        .then(function(mammals) {
            this.setState({ mammals });
        }.bind(this))
        .catch(function(error) {
            this.error = error;
        }.bind(this));
}


// ...
jsx

In this naive solution above, you can see how the bind function is being used to bind the context of this into the catch block so that the error can be shown by the component. This works ... but there's a better way! In the next section, you will see how arrow functions can be used to greatly simplify this.

Binding Component Scope Using Arrow Functions

Arrow functions are so much more than just succinct means of declaring functions. Arrow functions also implicitly bind the this keyword for you! In the following code, you will see how we can access the error property of our MammalsComponent through arrow functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ... MammalComponent.jsx

// We don't have to bind these functions anymore!
fetchMammals = () => fetch(this.mammalsUrl).then(response => response.json())

initMammalsState = () => {
    const mammalJsonPromise = this.fetchMammals();

    mammalJsonPromise
        .then(mammals => this.setState({ mammals }))
        .catch(error => this.error = error);
}

// ...
jsx

Wow! Look at how much simpler the code is! This code not only works, but is cleaner and more maintainable. The catch block now has access to the MammalComponent's scope so that it now can set the error property.

Conclusion

Arrow functions are the best way to bind the context of a React component's scope to inner functions within the component. Using arrow functions in this fashion allows you to effectively access the this property within promise catch blocks.

There are some additional improvements that could be made to the above code, namely the use of async/await syntax. The async and await JavaScript keywords can allow you to simplify promise usage and cut down on the amount of nested code you have in your codebase. It also allows you to model asynchronous code in a synchronous manner. For more information, check out the documentation for async/await syntax.

0