Jordan Irabor Jordan is an innovative software developer with over five years of experience developing software with high standards and ensuring clarity and quality. He also follows the latest blogs and writes technical articles as a guest author on several platforms.

How to access the correct this inside a callback

4 min read 1375

How to Access the Correct `this` Inside a Callback

Writing JavaScript can be a menace to both rookie and experienced developers alike due to some of its unorthodox implementations of popular programming concepts. This article tackles the scenario where two tricky concepts work hand in hand to frustrate the unsuspecting programmer:

  1. Callbacks
  2. this (context)

Each of these can already be a nightmare to work with, but it gets even trickier when the challenge is to access the correct this within a callback. In this article, we will figure this out and see how we can explicitly force a context binding to point to our object of choice.

For us to tread gently, we have to recap what a callback is.

What is a callback?

A callback is a function that is passed as an argument to another function. Usually, the callback is then invoked at some point within the outer function.

Note: The outer function that takes in a callback is called a higher-order function.

Since a callback is a function and functions are objects in JavaScript, a callback has its own set of methods and properties. When a callback is executed within a higher-order function, it gets assigned a this property that is completely dependent on how it is invoked and not where/how/when it was defined.

We can trace the this value within a callback by looking within the higher-order function where it is invoked. Most of the problems with this in callbacks are due to the fact that the actual definition of the enclosing function might have locally scoped properties. When that property is accessed using a this binding within the callback, however, it doesn’t exist because the context of the callback changes dynamically depending on how it is invoked.

Pro tip: When a function (callback) is invoked, the JavaScript interpreter creates an execution record (execution context), and this context contains information about the function. Amongst other things is the this reference, which is available for the duration of the function’s execution.

Here’s an example of a callback function:

function HOF(callback){
  callback(); 
}

function callback(){
  console.log(this);
}

HOF(callback) // points to the global Window Object

In the example above, we have a function called HOF (higher-order function), and it takes in a callback function that logs its this value to the console.

This is a great example of tracing down the this value within the callback to see where it is invoked because the context of a callback changes, and its this value is reassigned depending on how it is being invoked within the enclosing function.

Note: In a callback that is invoked by an enclosing function, the this context changes. The value this holds is reassigned to the function that is calling the function — the call site.

In this case, the enclosing function — HOF — is defined and called in the global scope so the this binding within the callback will point to the Window object.

Note: The Window object is a client object that represents an open window in the browser.

Let’s have a look at some of the behaviors of the this value when used under different scenarios:

function bar() {
    console.log(this);
}

bar(); // points to the global Window Object

This is pretty straightforward. The bar() function is in the global scope, so its this value will point to the Window object. If we took that same function and made it into a method on an object, however, we get a different binding:

let sample = {bar: bar};

sample.bar(); // points to the object above

The output of this code will point to the sample object we just created. This is perhaps the most expected and intuitive binding; we tend to expect the this value to refer to the object to the left-hand side of the dot, but this isn’t always the case in JavaScript.

And, finally, if used in a new constructor:

new bar();

The output of this code will point to an object that inherits from bar.prototype.

This is all fairly straightforward until we have situations with nested callbacks where it seems like a function should have a this binding that refers to its lexical enclosing function that possesses all the properties defined at author time. But at this point, we tend to overlook the fact that a function’s context binding is completely independent of its lexical declaration and is determined by how it is invoked.



When this becomes the case, there are a few ways to resolve bugs that arise from being unable to access the correct this in a callback.

3 methods for accessing the correct this inside a callback

1. Use an arrow function

JavaScript arrow functions were introduced in ECMAScript 6. They’re the compact alternative to a traditional function expression and do not have their own this binding. This ensures that whenever a reference to this is used within an arrow function, it is looked up in scope like a normal variable.

Let’s have a quick look at this Stack Overflow problem that’s centered around the this binding in a callback:

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', function () {
        console.log(this.data);
    });
}

// Mock transport object
let transport = {
    on: function(event, callback) {
        setTimeout(callback, 1000);
    }
};

// called as
let obj = new MyConstructor('foo', transport);

This is one of the trickier scenarios where the this binding within the callback refers to the Window object and seems difficult to trace and debug. When we run this code, it outputs undefined, but we can easily solve this problem by changing the anonymous function expression to an arrow function. The code then becomes:

[...]
    transport.on('data', () => {
        console.log(this.data);
    });
}
[...]

That’s it — it’s as easy as changing a few characters in the function declaration, and we’ve solved the this binding problem.

2. Create another variable to store the this object

Most times, when we try to access this within a callback, what we really want access to is the object it points to. A way to achieve this is to create a variable and store the value of this just before the callback scope (although some programmers would rather not because it seems messy).

I’ve seen some people call it that or self, but it really doesn’t matter what it’s called as long as it’s intuitive enough. This hack works because the variable obeys the rules of lexical scope and is therefore accessible inside the callback. An extra benefit to this method is that you still have access to whatever the dynamic this binding of the callback is.

Here’s an example of what it would look like using the snippet above:

function MyConstructor(data, transport) {
    this.data = data;
    let that = this;
    transport.on('data', function() {
        alert(that.data);
    });
}

This, like the solution before it, solves the problem of accessing this within a callback.

3. Explicitly bind this to an object

We can explicitly specify what we want this to be when we define a callback. Using the bind() method, we can set the this value and be certain that it’ll remain that way during its execution no matter how or where the function is called or passed.

Every function has the bind() method that returns a new function with its this property bound to a specified object. The returned function will have the exact behavior as the original function; the only difference is that you have complete control over what the this property points to.

Let’s take the same code snippet for example:

function MyConstructor(data, transport) {
    this.data = data;
    let boundFunction = (function() { 
        alert(this.data);             
    }).bind(this); // we call bind with the `this` value of the enclosing function
    transport.on('data', boundFunction);
}

This solves the problem and gives us great control over the this binding of the callback.

Conclusion

We’ve had a superficial exploration of two of the trickiest and most daunting concepts in modern JavaScript. Whenever you are within a codebase that has callbacks and it seems to be accessing the wrong this, try tracing the callback’s execution within the higher-order function to find a clue to what its this binding might be, depending on how the higher-order function is called.

If that fails or proves difficult, remember your arsenal of techniques in rectifying this menace.

: Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

.
Jordan Irabor Jordan is an innovative software developer with over five years of experience developing software with high standards and ensuring clarity and quality. He also follows the latest blogs and writes technical articles as a guest author on several platforms.

Leave a Reply