48

How can I call Function.prototype.bind with an array of arguments, as opposed to hardcoded arguments? (Not using ECMA6, so no spread operator).

I'm trying to put a promises wrapper around a module that uses callbacks and I want to bind all of the arguments passed in to my wrapper method and bind them. Then I want to call the partially applied bound function with my own callback, which will resolve or reject a promise.

var find = function() {
  var deferred, bound;
  deferred = Q.defer();
  bound = db.find.bind(null, arguments);
  bound(function(err, docs) {
    if(err) {
      deferred.fail(err);
    } else {
      deferred.resolve(docs);
    }
  });
  return deferred.promise;
}

But obviously this doesn't work because bind expects arguments rather than an array of arguments. I know I could do this by inserting my callback onto the end of the arguments array and using apply:

arguments[arguments.length] = function(err, docs) { ... }
db.find.apply(null, arguments);

Or by iterating over the arguments array and rebinding the function for each argument:

var bound, context;
for(var i = 0; i < arguments.length; i++) {
   context = bound ? bound : db.find;
   bound = context.bind(null, arguments[i]);
}
bound(function(err, docs) { ... })

But both of these methods feel dirty. Any ideas?

Dan Prince
  • 29,491
  • 13
  • 89
  • 120
  • I believe your last example is wrong. You just keep overwriting `bound` in each iteration. So you end up with `bound` bind `db.find` with the last argument bound to it. – Felix Kling Feb 02 '14 at 05:40
  • Wasn't concentrating when I jotted it down. Cheers for the heads up. – Dan Prince Feb 02 '14 at 05:46

11 Answers11

82

.bind is a normal function, so you can call .apply on it.
All you have to do is pass the original function as the first param and the desired THIS variable as the first item in the array of arguments:

bound = db.find.bind.apply(db.find, [null].concat(arguments));
//      ^-----^            ^-----^   THIS

Whether that can be considered cleaner or not is left to the reader.

James Harrington
  • 3,138
  • 30
  • 32
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • 3
    Of course. Fantastic! Cleaner in concept, but in practice? I think not. – Dan Prince Feb 02 '14 at 05:43
  • 1
    Yeah, personally I'd find it rather confusing. It would probably help to wrap this in a function, something like `function bindArray() { ... }`, which makes the intend clearer. – Felix Kling Feb 02 '14 at 05:45
  • 14
    A small note: [null].concat(arguments) yields [null, arguments] because arguments isnt treated like an array. Instead, this worked for me: var argsAsArray = Array.prototype.slice.call(arguments); bound = db.find.bind.apply(db.find, [null].concat(argsAsArray)); – Philipp Mar 12 '14 at 14:36
  • @Otts Arguments not being treated as an Array is also easily fixable, using Array.prototype.slice, since it accepts the arguments object and returns an Array. The whole function will then become `bound = db.find.bind.apply(db.find, [null].concat(Array.prototype.slice.call(arguments)));` – Martijn Otto Dec 10 '14 at 15:15
  • 1
    @MartijnOtto This is exactly what I wrote in my comment, isn't it? The only difference is that you inlined my definition of argsAsArray (which I wouldn't prefer for the sake of readability). – Philipp Dec 10 '14 at 18:20
  • It might be worth noting that the first item in the arguments will need to be the `this` object for the bind function – McShaman Jan 29 '15 at 19:57
  • 1
    Magnificent... callback.bind.apply(callback, [this, data]) -- Thx a ton! – Cody Feb 03 '15 at 15:07
  • This doesn't seem to work for me. Chaining _functionName.bind.apply(this, ['test', 'test2']) results in the following error: Uncaught TypeError: Bind must be called on a function. Am I missing something? – Zac Oct 02 '15 at 16:57
  • 1
    @Zeb: It doesn't seem `this` refers to a function in your case. If `_functionName` is a function, then you probably want `_functionName.bind.apply(_functionName, ['test', 'test2']) `. – Felix Kling Oct 02 '15 at 16:59
  • Ahh, gotcha. That must be it, i thought the first argument to apply was the context, not the function. Thank you ! – Zac Oct 02 '15 at 17:02
  • @Zeb: In this case you are calling `bind.apply`, i.e. you are setting the context of `.bind`, and that must be a function. I now think you want `_functionName.bind.apply(_functionName, [this, 'test', 'test2'])` – Felix Kling Oct 02 '15 at 17:03
  • Yeah, that makes a lot of sense actually. I realized that apply would take the array of args, and pass them to bind correctly, but overlooked the second context switch. – Zac Oct 02 '15 at 17:05
  • Javascript is causing serious damage to my brain sometimes :( – Alexander Derck Jun 21 '16 at 08:14
  • I know this is an old post, but using this resulted in 30% performance improvement of a function I was writing. – aaa Jan 21 '17 at 10:06
  • the first reference to `db.find` (or _functionName) is extraneous and (I would argue) confusing. The proper implementation is `bound = Function.bind.apply(db.find, [null].concat(arguments))` – codechimp Mar 21 '17 at 15:50
  • @codechimp: Yeah, I can see that. However, `Function.bind`, while maybe nice to read, is essentially the same as `db.find.bind` or `Array.bind`, or any other reference to `.bind` via a function object. `Function.prototype.bind` might be more correct. – Felix Kling Mar 22 '17 at 00:33
  • This answer should be edited to add slice(), as it is wrong as it stands. – Dirigible Nov 19 '19 at 17:36
  • How about using: ```var arg=[this,...Array.from(arguments)]; bound = db.find.bind(...arg);``` ? – Xerix Nov 19 '21 at 00:53
  • @Xerix: In that case I would just do `db.find.bind(this, ...Array.from(arguments))`. – Felix Kling Nov 19 '21 at 08:24
11

The following is a common snippet of code I use in all my projects:

var bind = Function.bind;
var call = Function.call;

var bindable = bind.bind(bind);
var callable = bindable(call);

The bindable function can now be used to pass an array to bind as follows:

var bound = bindable(db.find, db).apply(null, arguments);

In fact you can cache bindable(db.find, db) to speed up the binding as follows:

var findable = bindable(db.find, db);
var bound = findable.apply(null, arguments);

You can use the findable function with or without an array of arguments:

var bound = findable(1, 2, 3);

Hope this helps.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • 1
    Shouldn't this be `Function.prototype.bind` and `Function.prototype.call`? –  Nov 18 '14 at 13:08
  • 4
    @torazaburo It wouldn't really matter because `Function` is itself a function. Hence, it inherits `bind` and `call` from `Function.prototype`. Therefore, I used the shorter of the two forms because... I am a lazy sadist who likes to confuse people with terse code. – Aadit M Shah Nov 18 '14 at 13:34
  • 3
    Bindable is bind,bind,bind ? I feel a song coming on. – Kokodoko Apr 08 '16 at 11:37
9

Felix's answer didn't work for me because the arguments object isn't really an array (as Otts pointed out). The solution for me was to simply switch bind and apply:

bound = db.find.apply.bind(db.find, null, arguments);
Joel
  • 2,654
  • 6
  • 31
  • 46
  • 2
    I found this conceptually slightly more difficult (I wanted a bound function call, I have a bound apply call), but it makes sense (I call bound apply, function is called just as well) and is syntactically much clearer :). – Iiridayn May 12 '16 at 19:20
  • 4
    I find it easier to understand if written as `Function.apply.bind(db.find, null, arguments)`. The general `apply` method is bound to execute `db.find` with context set to `null` and arguments array set to `arguments`. – trincot Nov 09 '16 at 21:53
4

For those using ES6, Babel compiles:

db.find.bind(this, ...arguments)

to:

db.find.bind.apply(db.find, [this].concat(Array.prototype.slice.call(arguments)));

I think it's fair to say Babel is pretty definitive. Credit to @lorenz-lo-sauer though, it's almost identical.

nathancahill
  • 10,452
  • 9
  • 51
  • 91
1

Why not simply bind to the arguments array as per your example, and have the bound() function treat it just like that, as an array?

By the looks of your usage, you are then passing in a function as the final argument to bound(), which means by passing in the actual argument array, you avoid having to separate arguments from callbacks inside bound(), potentially making it easier to play with.

Matt Way
  • 32,319
  • 10
  • 79
  • 85
1

Generically, this schema suffices:

//obj = db
//fnName = 'find'
var args = [this||null].concat(Array.prototype.slice.apply(arguments);
obj[fnName].bind.apply(obj[fnName], args);
Lorenz Lo Sauer
  • 23,698
  • 16
  • 85
  • 87
1

I find following cleaner than the accepted answers

Function.bind.apply(db.find, [null].concat(arguments));
brillout
  • 7,804
  • 11
  • 72
  • 84
  • 1
    I did not know about Function.bind (but i used Function.prototype.bind). Is it part of any standard ? – 131 Nov 25 '16 at 09:17
1

If someone is looking for an abstract sample:

var binded = hello.apply.bind(hello,null,['hello','world']);

binded();

function hello(a,b){
  console.log(this); //null
  console.log(a); //hello
  console.log(b); //world
}
Paweł
  • 4,238
  • 4
  • 21
  • 40
0

Just had an alternative idea, partially apply the null value for context and then use apply to call the partially applied function.

bound = db.find.bind.bind(null).apply(null, arguments);

This removes the need for the slightly spooky looking [null].concat() in @Felix's answer.

Dan Prince
  • 29,491
  • 13
  • 89
  • 120
0

A definitive and simple answer might be

Function.apply.bind(this.method, this, arguments);

Kinda "hard" to grasp, yet, neat.

131
  • 3,071
  • 31
  • 32
0

Just a side remark in case someone lands here after reading only the title of the question. I know the OP didn't want to use ES6 spread operators and explicitly asked for a call to bind(), so my answer is apparently twice off-topic.

However, it appears the actual need was to apply bind() to an unbound function (i.e. not a method), which makes bind essentially unnecessary.

bind() has to handle special cases (like enforcing the value of this for a constructor) which costs some computation time that is wasted if you just want to apply some parameters to an ordinary function and don't care about this to begin with.

A naive version of bind() stripped of the sanity checks and OOP-specific fiddling with this could be:

Function.prototype.naive_bind = function (fixed_this, ...fixed_args) {
    const fun = this;
    return function(...free_args) {
        return fun.call(fixed_this, ...fixed_args, ...free_args);
    }
}

So you can write your own pseudo-bind that drops this and does a "partial application" of your actual parameters:

function partial_application (fun, ...applied_args) {
    return function(...free_args) {
        return fun.call(null, ...applied_args, ...free_args);
    }
}

If you want to apply all your arguments and leave room for a last one, you can drop the extra parameters too:

function total_application (fun, ...applied_args) {
    return function(free_arg) {
        return fun.call(null, ...applied_args, free_arg);
    }
}

If your arguments are inside an array, you can destructure them:

function total_application_from_array (fun, [...applied_args]) {
    return function(free_arg) {
        return fun.call(null, ...applied_args, free_arg);
    }
}

I suppose you could use apply instead of call, but you'd have to add the extra free parameter to the array.

function total_application_from_array (fun, applied_args) {
    return function(free_arg) {
        return fun.apply(null, [...applied_args, free_arg]);
    }
}

so you'd use that as:

var a = partial_application (console.log, 1, 2, 3);
a(4,5,6);             // 1, 2, 3, 4, 5, 6
a("hello", "world");  // 1, 2, 3, hello, world

a = total_application (console.log, 1, 2, 3);
a()                  // 1, 2, 3, undefined
a(4, 5, 6)           // 1, 2, 3, 4

a = total_application_from_array(console.log, [1,2,3]);
a(4,5,6)             // 1, 2, 3, 4
kuroi neko
  • 8,479
  • 1
  • 19
  • 43