Moussawi7
Moussawi7

Reputation: 13289

javascript: call function inside object, from a callback function

when i try to call a function inside the object using "this" from a callback function, an error occur saying that the method is undefined. How can I solve this issue!.

 var object_log = {
    user: "",
    pass: "",
    error_message: "an error occured while connecting",
    init: function(user, pass) {
        this.user = user;
        this.pass = pass;
    },
    login: function() {
        remote_submit(identify, this.success, this.error);
    },
    error: function() {
        alert(this.error_message);
    },
    success: function() {
        alert("success");
    }
};

Upvotes: 3

Views: 9374

Answers (2)

Aren
Aren

Reputation: 55976

You need to use the .call() or .apply() methods on the callback to specify the context which the method is called upon.

The callback method remote_submit does not know what this will be anymore and thus when it calls the callback methods they're executed like normal functions not on an object.

You can "Bind" your functions by wrapping them on the way out:

var self = this;
remote_submit(
  identify,
  function() { return self.success.apply(self, arguments); },
  function() { return self.error.apply(self, arguments); }
);

This allows you to pass the context in the closure of the anonymous function and execute the callbacks with an exclusive this context.

It appears that in EMCAScript5+ you can use bind on the function to bind it for use in a callback:

remote_submit(identify, this.success.bind(), this.error.bind())

However from the MDN Documentation:

The bind function is a recent addition to ECMA-262, 5th edition; as such it may not be present in all browsers. You can partially work around this by inserting the following code at the beginning of your scripts, allowing use of much of the functionality of bind() in implementations that do not natively support it.

The shim/polyfill is here:

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      // closest thing possible to the ECMAScript 5 internal IsCallable function
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, 
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instanceof fNOP && oThis
                                 ? this
                                 : oThis,
                               aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
  };
}

Update:

To answer your additional question, let's first look at the call and apply documentation and break down how they work:

Fundamentally they work the same, the only difference is how they take their arguments:

myfunc.call(target, param1, param2, param3);

Will call myfunc(param1, param2, param3) with target as this.

var args = [param1, param2, param3];
myfunc.apply(target, args);

Will call myfunc(param1, param2, param3) with target as this.

Basically the difference is that .apply() takes an array of arguments, where the call function requires you to write in the arguments in the code.

Next, if we look at the example i gave you:

function() { return self.success.apply(self, arguments); }

This returns a function that will call your callback by passing all the arguments (arguments variable) that were passed into the anonymous function, onto the apply function. So:

var a = function() { return self.success.apply(self, arguments); };
a(1,2,3,4);

This will call self.success(1,2,3,4) with self as this. If you'd like to augment the arguments with something specific for example if you wanted a(1,2,3,4) to call self.success(self.test, 1, 2, 3, 4) then you'll have to provide an augmented array to the apply function:

var a = function() {
  var args = [self.test];
  for(var i = 0; i < arguments.length; i++) args[] = arguments[i];
  return self.success.apply(self, args);
}

Upvotes: 4

Pointy
Pointy

Reputation: 413996

When you pass the function as a callback, do it like this:

  whatever( object_log.login.bind( object_log ) );

That call to the .bind method will return a function that'll make sure your "login" function will be called such that this references the "object_log" object.

There's a good shim for .bind for older browsers at the MDN documentation site.

Upvotes: 1

Related Questions