loading...

How to access “this” from inside a callback

logicmason profile image Mark Originally published at logicmason.com on ・2 min read

JavaScript’s keyword this is a source of pain for a lot of people. A common question goes something like this:

“I make call a Firebase function, and inside that callback, I need to access the this to update my React component.”

Another common case is setTimeout:

“The problem is setTimeout takes a callback, but I need to access this and setTimeout changes it!”

A basic guideline

In normal circumstances, this refers to whatever is to the left of the dot at calltime. There are a few exceptions, which specifically alter the behavior of this—call, apply and bind. If you haven’t used one of those functions to alter the behavior of this, then look to the left of the dot at calltime.

An example

var alarm = {
  ringer: function () {
    console.log("The " + this.color + " alarm: Ring!!!");
  },
  color: "red"
}

alarm.ringer();

This is pretty straight-forward. When you call alarm.ringer();, the thing to the left of the dot is alarm. So inside the ringer function, this is the alarm and the color is red.

An error in a setTimeout callback

var alarm = {
  ringer: function () {
    console.log("The " + this.color + " alarm: Ring!!!");
  },
  set: function (milliseconds) {
    setTimeout(function () { this.ringer() }, milliseconds);
  },
  color: "red"
}

alarm.set(2000);

The goal here was that the alarm would ring two seconds later. Unfortunately, we get this error:

The reason is that setTimeout calls the callback passed into it. Based on the error, we can see that the setTimeout function created a Timeout object and stored the callback function we passed into it as Timeout._onTimeout. And at the time it was called , the thing to the left of the dot is the timeout, not the alarm.

The classic solution

The way the “this” problem has traditionally been solved is by assigning the “this” from the outer scope to a new variable called either “_this” or “that”.

var alarm = {
  ringer: function () {
    console.log("The " + this.color + " alarm: Ring!!!");
  },
  set: function (milliseconds) {
    var that = this;
    setTimeout(function () { that.ringer() }, milliseconds);
  },
  color: "red"
}

alarm.set(2000);

Now it works. Since the value of “this” inside the set function is the alarm, and that is just a regular variable. Since functions can access variables from their surrounding scope, that.ringer() inside the callback is alarm.ringer().

The red alarm: Ring!!!

The ES6 arrow function solution

ES6 arrow functions don’t work quite the same way as normal function expressions. They don’t have their own bindings to “this”, so any references to “this” use the bindings from the enclosing function.

let alarm = {
  ringer: function () {
    console.log("The " + this.color + " alarm: Ring!!!");
  },
  set: function (milliseconds) {
    setTimeout(() => { this.ringer() }, milliseconds);
  },
  color: "red"
}

alarm.set(2000);

As the MDN link above mentions, arrow functions are ill-suited for methods, but they are a convenient way to simplify callbacks. In 2019, most JS devs use arrow functions to solve this problem and only encounter the “this = that” pattern in libraries and legacy code.

Subscribe to more content from logicmason.com

Discussion

pic
Editor guide
Collapse
smlka profile image
Andrey Smolko

Hey, nice post=) If you are interested in I did a post about setTimeout and this in callback (I considered a browser as environment and actually in node.js this is defined in different way):
dev.to/smlka/easyspec-how-does-set...

Collapse
marklai1998 profile image
Mark Lai

I use arrow function 100% of time
I think that is much cleaner
Plus I can't think any reason to scope this inside a function (just don't make sense to me, scope in class maybe)

Collapse
matteorigon profile image
Matteo Rigon

Actually there are cases where you want to have the inner scope rather then the outer:


const obj = {
  x: 10,
  foo() {
      return {
        x: 20,
        printX() {
          console.log(this.x); // prints 20
        }
      }
  },
  bar() {
      return {
        x: 20,
        printX: () => console.log(this.x) // prints 10
      }
  },
}

Collapse
marklai1998 profile image
Mark Lai

Sorry, this pattern is just worse, really worse
Hope you won't type this in real world

Collapse
logicmason profile image