JavaScript, TypeScript

TypeScript, ‘this’ Scope and JavaScript callbacks

Using ‘setInterval’ caught me out today

export class SetIntervalTest {

    private someNumber: number = 1;

    trigger() {
        setInterval(this.setIntervalCallback, 400);
    }

    private setIntervalCallback() {
        console.log(this.someNumber);
    }
}

Now given any object orientated library, you would expect that after every 400 milliseconds you would get a ‘1’ in the output log. ‘setIntervalCallback’ is a member function of SetIntervalTest, it references ‘this’ which scope out dicate to be the instance of SetIntervalTest we’re running in.

But instead of a simple ‘1’, we get ‘undefined’.

Here’s the generated JavaScript

"use strict";
var SetIntervalTest = (function () {
    function SetIntervalTest() {
        this.someNumber = 1;
    }
    SetIntervalTest.prototype.trigger = function () {
        setInterval(this.setIntervalCallback, 400);
    };
    SetIntervalTest.prototype.setIntervalCallback = function () {
        console.log(this.someNumber);
    };
    return SetIntervalTest;
} ());
exports.SetIntervalTest = SetIntervalTest;
//# sourceMappingURL=interval-test.js.map

Even now, it looks to me like ‘this.someNumber’ should be within the scope of SetIntervalTest, especially as it’s the same structure of ‘SetIntervalTest.prototype.trigger’. But instead it’s pulling ‘this’ in SetIntervalTest.prototype.setIntervalCallback from the windows scope, hence someNumber is undefined.

Since, through the call to setInterval, our object method wasn’t a result of a binded function call, the body of setIntervalCallback is now referring to the windows scope, rather than the objects scope.

Not obvious in any way based on the structure of the TypeScript.

To get the correct behaviour we have to change

setInterval(this.setIntervalCallback, 400);

to

setInterval(() => this.setIntervalCallback(), 400);

This now generates the following JavaScript

"use strict";
var SetIntervalTest = (function () {
    function SetIntervalTest() {
        this.someNumber = 1;
    }
    SetIntervalTest.prototype.trigger = function () {
        var _this = this;
        setInterval(function () { return _this.setIntervalCallback(); }, 400);
    };
    SetIntervalTest.prototype.setIntervalCallback = function () {
        console.log(this.someNumber);
    };
    return SetIntervalTest;
} ());
exports.SetIntervalTest = SetIntervalTest;
//# sourceMappingURL=interval-test.js.map

The only difference is that we’re capturing the scope of this before calling setIntervalCallback which results in this.someNumber being correctly scoped.

To me this is clearly a bug in either the transpiler or the compiler not picking up the issue (either as a warning or an error), as such I’ve raised an issue here
https://github.com/Microsoft/TypeScript/issues/10285

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s