Vinz243
Vinz243

Reputation: 9938

Apllying arguments in javascript doesn't work

I am trying to make a small utility function toPromise that transforms a function using callback into a promise function (using Q). Here is the utility:

Object.defineProperty(Function.prototype, "toPromise", {
  enumerable: false,
  configurable: false,
  writable: false,
  value: function(self) {
    var $this;
    $this = this;
    return function() {
      var args, deferred;
      deferred = Q.defer();
      args = arguments;
      args[args.length] = deferred.resolve;
      console.dir(args);
      $this.apply(self, args);
      return deferred.promise;
    };
  }
});

Here is the usage:

var f = function(nickname, name, callback){
  console.log(arguments)
  callback(undefined, name+" "+nickname);
};
f.toPromise(this)("a", "b").then(function(str){
  console.log(str)
});

But the output is:

{ '0': 'a', '1': 'b', '2': [Function] } // This is debugging from utils
{ '0': 'a', '1': 'b' } // and from the function f

C:\test.js:4
  callback(undefined, name+" "+nickname);
  ^
TypeError: undefined is not a function

So why is the third arg (in utils), the function, not passed in the apply ?

Upvotes: 1

Views: 83

Answers (2)

Bergi
Bergi

Reputation: 664599

You did assign the callback to the arguments object, but since it's not an actual array that did not automatically increase its length property. With length still being 2, your callback was simply skipped in the apply.

The fix is simple:

Function.prototype.toPromise = function(self) {
    var fn = this;
    return function() {
        var deferred = Q.defer();
        arguments[arguments.length++] = deferred.resolve;
//                                ^^
        fn.apply(self||this, arguments);
        return deferred.promise;
    };
};

You also might use Array.prototype.push.call(arguments, deferred.resolve). Notice that Q already provides such similar1 a functionality (with probably optimized efficiency), so if you just want to use it on the prototype you'd better do

Function.prototype.toPromise = function(self) {
    return Q.nbind(this, self);
};

1: Notice that resolve() does not take an error argument, like "nodebacks" usually do

Upvotes: 2

Kos
Kos

Reputation: 72271

The arguments object is not a real array:

>>> function getArguments() { return arguments; }
>>> var a = getArguments(1,2,3), b = [1,2,3];
>>> a[3] = 4; b[3] = 4;
>>> a
[1, 2, 3]
>>> b
[1, 2, 3, 4]
>>> a.length
3
>>> b.length
4

so if you intend to modify it or pass it around to outside the function it's good to convert it to a real array:

var args = Array.prototype.slice.call(arguments);

Upvotes: 2

Related Questions