views:

40

answers:

3

I am having some trouble getting a callback function to work. Here is my code:

SomeObject.prototype.refreshData = function()
{
  var read_obj = new SomeAjaxCall("read_some_data", { }, this.readSuccess, this.readFail);
}

SomeObject.prototype.readSuccess = function(response)
{
    this.data = response;
    this.someList = [];
    for (var i = 0; i < this.data.length; i++)
    {
      var systemData = this.data[i];
      var system = new SomeSystem(systemData);
      this.someList.push(system);
    }
    this.refreshList();
}

Basically SomeAjaxCall is making an ajax request for data. If it works we use the callback 'this.readSuccess' and if it fails 'this.readFail'.

I have figured out that 'this' in the SomeObject.readSuccess is the global this (aka the window object) because my callbacks are being called as functions and not member methods. My understanding is that I need to use closures to keep the 'this' around, however, I have not been able to get this to work.

If someone is able show me what I should be doing I would appreciate it greatly. I am still wrapping my head around how closures work and specifically how they would work in this situation.

Thanks!

+2  A: 

Well the most straightforward thing to do is to just wrap "this.readSuccess" in another function:

SomeObject.prototype.refreshData = function()
{
  var obj = this;
  var read_obj = new SomeAjaxCall("read_some_data", { }, 
    function() { obj.readSuccess(); }, function() { obj.readFail(); });
}

Some Javascript frameworks provide a utility to "bind" a function to an object, which simply means that it creates one of those little functions for you. Note that the variable "obj" will be "remembered" by those little functions, so when your handlers are called the "this" reference will be to the object that was used to call "refreshData".

Pointy
Indeed, what happens here is that `obj` is being locked in the closure created when `function() { obj.readSuccess(); }` is evaluated. There is no other way to do this unless you use a library like Prototype or jQuery; these have a `.bind()` function that will do the closure creation transparently.
MvanGeest
Not to nit-pick but for the sake of StackOverflow posterity the jQuery version is called "proxy", not "bind".
Pointy
Makes perfect sense. The jQuery proxy function works, that's great.For completeness, however, I'm still a little confused how I would handle this if I were not using jQuery. In my example the readSuccess has 1 argument, respnse, which is populated at a later date. So it doesn't make sense to me to say function() { obj.readSuccess(wah??); } since the wah?? is supposed to be getting passed in at a later time.I tried function() { obj.readSuccess; } with no luck. Any thoughts?
nazbot
You are perfectly right, nazbot. We should not suppose that `readSuccess` is called without argument, since it's probably not the case. See my answer for a solution.
Alsciende
A: 

The following site seems to suggest that the problem may be more in your "class" building than in the usage. If you read down to the "rewrite using prototype properties", they write that that particular method of "class" structuring will keep the methods global instead of instance-based. Perhaps another creation method?

http://devedge-temp.mozilla.org/viewsource/2001/oop-javascript/

Lance May
A: 

Your problem here is not exactly a closure or scoping problem. The problem is that when you assign this.readSuccess to a variable, you assign the function itself without any notion of the object it originaly belongs to.

In the same way, you can take a regular, "stand-alone" function and use it as method of an object:

function hello() {
    alert("Hello "+this.planet);
}
var planet = "Earth";
hello(); // -> 'Hello Earth'

var Venus = {
    planet: "Venus"
};
hello.apply(Venus); // -> 'Hello Venus'
Venus.hello = hello;
Venus.hello(); // -> 'Hello Venus'

And your problem can be replicated in this example

var helloVenus = Venus.hello;
helloVenus(); // -> 'Hello Earth'

So your problem is to assign this.readSuccess to some variable and having it called as a method of this. Which can be done with a closure as demonstrated by Pointy. Since I don't know what "SomeAjaxCall" actually does, it's hard to know if the value of this is actually lost and if var obj = this is actually needed. Chances are that it's not, so you can be fine with this kind of code:

var helloVenus = function() { Venus.hello() }
helloVenus(); // -> 'Hello Venus'

In your case, that would be (edit: adding the arguments passed to the handler) :

SomeObject.prototype.refreshData = function()
{
  var read_obj = new SomeAjaxCall(
    "read_some_data",
    { },
    function () { this.readSuccess.apply(this, arguments) },
    function () { this.readFail.apply(this, arguments) }
  );
}

As noted previously, several js frameworks offer a bind function to simplify this kind of issue. But you don't need a complete framework just for this : here is a perfectly fine Function#bind method that works an plain javascript:

Function.prototype.bind = function(obj) {
    var __method = this;
    var args = [];
    for(var i=1; i<arguments.length; i++)
        args.push(arguments[i]);
    return function() {
        var args2 = [];
        for(var i=0; i<arguments.length; i++)
            args2.push(arguments[i]);
        return __method.apply(obj, args.concat(args2));
    };
}

With the help of Function#bind, you can write:

SomeObject.prototype.refreshData = function()
{
  var read_obj = new SomeAjaxCall(
    "read_some_data",
    { },
    this.readSuccess.bind(this),
    this.readFail.bind(this)
  );
}
Alsciende
This is a great answer. Thanks a lot for the info.
nazbot