Skip to content Skip to sidebar Skip to footer

Keep Babel Class As `this` When Using A Member Function In `settimeout`

I have an ES2015 class, call it Foo, which has at least two member functions, bar and baz. In bar there is a call to setTimeout whose first parameter is this.baz. Works fine up to

Solution 1:

My question is, am I doing something wrong with ES2015 or babel here?

Actually, it's a expected JavaScript behavior and is related to how this is assigned in the language.

Consider the code below (no ES6, no babel...):

var obj = {
   key1: 'value1',
   key2: function() {
     console.log(this);
   }   
}

obj.key2(); //will print objvar callback = obj.key2; //assigned the function reference to some random variablecallback(); //will print Window/global object

As you can see, this is defined when the function is invoked, not when it's declared, and depends how it's being called.

That's exactly what's happening inside setTimeout, or in any function that receives a function as a parameter:

/* fake */
function setTimeout(fnCallback, time) {
    /* wait and when the time comes, call your callback like this: */fnCallback(); //'this' will be Window/global
}

"Workarounds":

In order to pass the desired context (in the example above), we can force the context:

  1. using .bind:

    var callback = obj.key2.bind(obj);
    callback(); //will print obj
    
  2. or using .call:

    var callback = obj.key2;
    callback.call(obj); //will print obj
    

Or we can pass an anymous function an call our object from inside:

setTimeout(function() {
   //here, 'this' is Window/global, because the anonymous function is being called from a callback assignment
   obj.key2(); //will print obj
}, 3000);

In your example

So, in your example, in order to properly set the setTimeout callback and ensure that baz() will receive the class context, you can:

  1. bind the function when setting it as a callback:

    setTimeout(this.baz.bind(this), 1000);
    
  2. in your class constructor, bind the baz method once; so, everytime it's called, will be assigned the class context. Like this:

    classFoo{
        constructor() {
            this.baz = this.baz.bind(this)
        }
        bar(){
            setTimeout(this.baz, 1000);
        }
        baz(){
            console.log("this message should repeat roughly once per second");
            this.bar();
        }
    }     
    
  3. Use arrow functions. Another way of specifying the this context is using arrow functions, that, actually, ensure the this assignment is done through lexical scope (not anymore in the function invocation, but in the function declaration).

    setTimeout(() =>this.baz(), 1000);
    //               ^^^^//               'this' here is your class, will pass your class as 'this'//                to the baz() method, due to the dot before

    Different from:

    setTimeout(function() { this.baz(); }, 1000);
    //                      ^^^^//                      'this' here is Window/global, will thrown undefined method

Post a Comment for "Keep Babel Class As `this` When Using A Member Function In `settimeout`"