7

I have problem which still bothers me on js oop - I'm sure I'm doing it bad, but I cant get how to do it right.

For example, I have this code

Auth.prototype.auth = function () {
    var request = new XMLHttpRequest();

    request.open('GET', this.getAuthServerURL() + '/token', true);
    request.send();

    request.onloadend = function () {
      var response = JSON.parse(request.responseText);

      console.log(response);
      if(response.result == 'found') {
        var token = response.token;

        this.setToken(token);
        this.isSigned = true;
      } else {
        console.log('Not logged yet.');
      }
    }
}

The problem is that I cant access to function setToken from context of "request.onloadend" function - its probably because I lost reference to "this".

Whats a solution of this problem? Can I somehow pass the "this" var to context of this function?

Thanks!

xdazz
  • 158,678
  • 38
  • 247
  • 274
yety
  • 701
  • 2
  • 16
  • 26

6 Answers6

5

There are a couple of ways to do this. The most direct is to simply save a copy of the value you need:

Auth.prototype.auth = function () {
    var request = new XMLHttpRequest();
    var self = this; // save "this" value

    request.open('GET', this.getAuthServerURL() + '/token', true);
    request.send();

    request.onloadend = function () {
      var response = JSON.parse(request.responseText);

      console.log(response);
      if(response.result == 'found') {
        var token = response.token;

        self.setToken(token); // use saved "this" value
        self.isSigned = true;
      } else {
        console.log('Not logged yet.');
      }
    }
}

Another way is to use bind:

request.onloadend = (function () {
  var response = JSON.parse(request.responseText);

  console.log(response);
  if(response.result == 'found') {
    var token = response.token;

    this.setToken(token); // use saved "this" value
    this.isSigned = true;
  } else {
    console.log('Not logged yet.');
  }
}).bind(this);

The second approach is "cleaner", but it has browser compatibility issues (IE < 9 does not support it).

Jon
  • 428,835
  • 81
  • 738
  • 806
  • I think the other is more direct, the code is minimally changed and you don't need to decide which pseudo keyword to use. Just saying. – Esailija Jul 09 '12 at 10:48
  • @Esailija: Me too, but sadly its practical value is constrained by browser compatibility. – Jon Jul 09 '12 at 10:49
2

.bind the function:

Auth.prototype.auth = function () {
    var request = new XMLHttpRequest();

    request.open('GET', this.getAuthServerURL() + '/token', true);
    request.send();

    request.onloadend = function () {
      var response = JSON.parse(request.responseText);

      console.log(response);
      if(response.result == 'found') {
        var token = response.token;

        this.setToken(token);
        this.isSigned = true;
      } else {
        console.log('Not logged yet.');
      }
    }.bind(this); //<-- bound
}
Esailija
  • 138,174
  • 23
  • 272
  • 326
1

You can just capture a reference to it in the outer scope, I've used the identifier self, however please feel free to give the name a more semantic meaning:

var self = this;
request.onloadend = function () {
  ...
  self.setToken(token);
  ...
};
Rich O'Kelly
  • 41,274
  • 9
  • 83
  • 114
1

Capture this before the callback:

Auth.prototype.auth = function () {
    var self = this;

    var request = new XMLHttpRequest();

    request.open('GET', this.getAuthServerURL() + '/token', true);
    request.send();

    request.onloadend = function () {
      var response = JSON.parse(request.responseText);

      console.log(response);
      if(response.result == 'found') {
        var token = response.token;

        self.setToken(token);
        self.isSigned = true;
      } else {
        console.log('Not logged yet.');
      }
    }
}
Matt Ball
  • 354,903
  • 100
  • 647
  • 710
0

Save this in a local var outside the callback.

Auth.prototype.auth = function () {
    var request = new XMLHttpRequest();
    var _this = this;

    request.open('GET', this.getAuthServerURL() + '/token', true);
    request.send();

    request.onloadend = function () {
      var response = JSON.parse(request.responseText);

      console.log(response);
      if(response.result == 'found') {
        var token = response.token;

        _this.setToken(token);
        _this.isSigned = true;
      } else {
        console.log('Not logged yet.');
      }
    }
}
xdazz
  • 158,678
  • 38
  • 247
  • 274
0

You are quite right: the callback is called with the XMLHTTPRequest object as the context (i.e. the value of this). You need to give your instance another name, so that you can access it within the scope of the callback:

Auth.prototype.auth = function () {
    var request = new XMLHttpRequest(),
        authInstance = this;

    request.open('GET', this.getAuthServerURL() + '/token', true);
    request.send();

    request.onloadend = function () {
      var response = JSON.parse(request.responseText);

      console.log(response);
      if(response.result == 'found') {
        var token = response.token;

        authInstance.setToken(token);
        authInstance.isSigned = true;
      } else {
        console.log('Not logged yet.');
      }
    }
}

See this answer to another question for more explanation of why this is necessary. I have used authInstance rather than self, because I think it's generally good to use descriptive variable names; you'll never have to work out what authInstance means, whereas self might be ambiguous when someone in the future (possibly you!) reads the code.

Another option is to use bind, but that is probably more complicated than necessary here.

Community
  • 1
  • 1
lonesomeday
  • 233,373
  • 50
  • 316
  • 318
  • Modifying most of the code and using 2 separate "keywords" as well as an extra variable is less complicated than simply throwing `.bind(this)` at the end? – Esailija Jul 09 '12 at 11:00
  • @Esailija I think the `bind` form is less intuitive and legible. The context only becomes clear at the end of the function, and the function is quite long. If it were a two-line function, I'd agree with you. – lonesomeday Jul 09 '12 at 11:02
  • Inside a class `this` referring to the current instance of the class ([no matter how the binding is implemented](http://en.wikipedia.org/wiki/Dynamic_dispatch)), is more intuitive in my opinion. But we can disagree on that. – Esailija Jul 09 '12 at 11:09