3

Suppose I have a function like the following.

var func = function(a, b, c) {
    console.log(a);
    console.log(b);
    console.log(c);
}

And what I want to do is call it by providing it's arguments inside an array like so:

func.apply(null, [1, 2, 3]);

which works as expected. Now, if I wanted to have that function called asynchronously, for example using a setTimeout, how could I do that? I tried the following:

setTimeout(func.bind.apply(null, [null, 1, 2, 3]), 1000);

but it gives me an error:

TypeError: Bind must be called on a function

Is there an elegant way to do this?

hardas
  • 33
  • 2

4 Answers4

3

There are quite a few ways to do this.

Method 1: setTimeout extra parameters

Did you know that you can pass setTimeout extra parameters just like you can do with call?

setTimeout(func, 1000, 1, 2, 3);

However suppose you have an array of arguments instead:

setTimeout(func, 1000, [1, 2, 3]); // This will not work

So you need to do the following instead:

setTimeout(function (args) {
    return func.apply(this, args);
}, 1000, [1, 2, 3]);

Unfortunately this won't work in older versions of Internet Explorer. However you could always do this:

function applyAsync(f, ms, args) {
    return setTimeout(function () {
        return f.apply(this, args);
    }, ms);
}

You call it as follows:

applyAsync(func, 1000, [1, 2, 3]);

In my opinion this is the cleanest and the fastest solution. However if you want a cleverer solution then:

Method 2: bindable, callable and appliable

My favourite functions in JavaScript:

var bind = Function.prototype.bind;
var call = Function.prototype.call;
var apply = Function.prototype.apply;

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

I won't explain how it works in detail but this is what you need to know:

  1. The bindable function takes a function f and returns a function equivalent to f.bind; and f.bind is partially applied to any additional parameters.
  2. The callable function takes a function f and returns a function equivalent to f.call; and f.call is partially applied to any additional parameters.
  3. The appliable function takes a function f and returns a function equivalent to f.apply; and f.apply is partially applied to any additional parameters.

The function you are looking for is appliable:

setTimeout(appliable(func, null, [1, 2, 3]), 1000);

This is as clever and elegant as it gets.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • Nice :-) For explanations, see [Explain bindbind() function](http://stackoverflow.com/q/13504936/1048572) and [How does bindbind(call) work](http://stackoverflow.com/q/23482982/1048572) – Bergi May 18 '14 at 12:05
2

You either want

Function.apply.bind(func, null, [1, 2, 3])

or

Function.bind.apply(func, [null, 1, 2, 3])

Currently you're applying bind on null, not on func, which will throw the error.

Instead of accessing .apply/.bind on Function, you could as well use Function.prototype or func.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • The current context is `window`, not `null`. – Guffa May 18 '14 at 11:28
  • @Guffa: not when it is bound to `null`? I don't understand. The OP does not want to bind to `window` (and in strict mode, you wouldn't anyway). – Bergi May 18 '14 at 11:30
  • Ah, I see what you mean, you are correct. The point that I was trying to make is that the value of `func.bind` is just a reference to `bind`, it has no relation to `func` any more. If called, it would get the global context instead of `func`, but then apply replaces that with `null`. – Guffa May 18 '14 at 13:24
1

You can pass arguments to Function.prototype.bind itself, like this

setTimeout(func.bind(null, 1, 2, 3), 1000);

Online Demo

The error you are getting is because bind will operate only on a function object, so the first argument to apply should be the actual function object, like this

setTimeout(func.bind.apply(func, [null, 1, 2, 3]), 1000);
GôTô
  • 7,974
  • 3
  • 32
  • 43
thefourtheye
  • 233,700
  • 52
  • 457
  • 497
  • That would work if the arguments I want to pass to "func" weren't inside an array. That's why I'm trying to use apply, unsuccessfully so far. – hardas May 18 '14 at 11:27
  • @hardas That is why I have suggested a way to fix your code in the second part. – thefourtheye May 18 '14 at 11:28
1

You can apply Function.prototype.bind this way, it just requires you to pass null to the first element of your array and you should be okay.

var myFun = function (a,b,c) {
    console.log(a,b,c);
}, myFunBound;

myFunBound = Function.prototype.bind.apply(myFun, [null, 1,2,3]);

setTimeout(myFunBound, 1000);
axelduch
  • 10,769
  • 2
  • 31
  • 50