Skip to content

Apply, Apply

Posted on:July 19, 2015

While going through Jasmine’s source code, I came across a weird idiom:

function attemptAsync(queueableFn) {
  var clearTimeout = function () {
      Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeoutId]]);
    },
    next = once(function () {
      clearTimeout(timeoutId);
      self.run(queueableFns, iterativeIndex + 1);
    }),
    timeoutId;
  ...
}

What in the world is Function.prototype.apply.apply? I decided to find out for myself.

As a refresher, let’s write a function coolFx that simply prints out its this and arguments. When invoked as a method on a function, apply executes its caller with its first parameter as the caller’s this, and its second parameter as an array of arguments as the caller’s argument list. The parameters to apply can be anything, even primitives, as demonstrated below:

function coolFx() {
  console.log("My cool function is running!!");
  console.log("coolFx's this:", this);
  console.log("coolFx's arguments:", arguments);
}
 
var anything = 1;
 
coolFx.apply(anything, [anything, anything, anything]);

prints:

My cool function running!!
coolFx's this: Number {[[PrimitiveValue]]: 1}
coolFx's arguments: [1,1,1]

To preserve our sanity later on, let’s see apply in more detail by defining a wrapper around apply:

Function.prototype.firstApply = function (x, y) {
  console.log("This is firstApply's this: ", this);
  console.log("This is firstApply's first parameter:", x);
  console.log("This is firstApply's second parameter:", y);
  this.apply(x, y);
};
Function.prototype.secondApply = function (x, y) {
  console.log("This is secondApply's this: ", this);
  console.log("This is secondApply's first parameter:", x);
  console.log("This is secondApply's second parameter:", y);
  this.apply(x, y);
};

Running coolFx.firstApply with this modified version of apply prints:

This is firstApply's this:  coolFx()
This is firstApply's first parameter: 1
This is firstApply's second parameter: [1]

My cool function is running!!
coolFx's this: Number {[[PrimitiveValue]]: 1}
coolFx's arguments: [1]

So what does it mean when you call apply again on itself, as in coolFx.firstApply.secondApply?

coolFx.firstApply.secondApply(anything, [anything, anything]);
This is the secondApply's this: Function.firstApply(x, y)
This is the secondApply's first parameter: 1
This is the secondApply's second parameter: [1, 1]

This is the firstApply's this: Number {[[PrimitiveValue]]: 1}
This is the firstApply's first parameter: 1
This is the firstApply's second parameter: 1

Uncaught TypeError: this.apply is not a function

Calling secondApply on firstApply means that secondApply will execute firstApply, and since firstApply needs to be executed in the context of a function, the first parameter must be a function. (I only know this from working backwards from the interpreter’s errors, go figure).

In this case, since this in firstApply is the primitive 1, 1.apply is obviously not a function since it doesn’t inherit from Function.prototype.

Now that we know that the secondApply expects a function as its first parameter, let’s try putting coolFx there:

coolFx.firstApply.secondApply(coolFx, [anything, anything]);
This is the secondApply's this: Function.firstApply(x, y)
This is the secondApply's first parameter: coolFx()
This is the secondApply's second parameter: [1, 1]

This is the firstApply's this: coolFx()
This is the firstApply's first parameter: 1
This is the firstApply's second parameter: 1

Uncaught TypeError: Function.prototype.apply: Arguments list has wrong type

Okay, so at least it’s a different error now. Why is firstApply complaining that its argument list (the second parameter) has the wrong type? That’s because its trying to call coolFx.apply(1,1)! Don’t forget that apply expects an array as its second parameter.

So I guess we can wrap the second anything in an array:

coolFx.firstApply.secondApply(coolFx, [anything, [anything]]);
This is the secondApply's this: Function.firstApply(x, y)
This is the secondApply's first parameter: coolFx()
This is the secondApply's second parameter: [1, 1]

This is the firstApply's this: coolFx()
This is the firstApply's first parameter: 1
This is the firstApply's second parameter: [1]

My cool function is running!!
coolFx's this: Number {[[PrimitiveValue]]: 1}
coolFx's arguments: [1]

So it finally works! Wait, doesn’t this look… familiar? It should, because what its doing is exactly what coolFx.apply(anything, [anything]) is doing (see above) - executing coolFx with this as anything and its arguments as [anything].

And isn’t it stupid that we’re mentioning coolFx twice? What exactly does the first coolFx even do? Let’s get rid of it:

Function.prototype.firstApply.secondApply(coolFx, [anything, [anything]]);
This is the secondApply's this: Function.firstApply(x, y)
This is the secondApply's first parameter: coolFx()
This is the secondApply's second parameter: [1, 1]

This is the firstApply's this: coolFx()
This is the firstApply's first parameter: 1
This is the firstApply's second parameter: [1]

My cool function is running!!
coolFx's this: Number {[[PrimitiveValue]]: 1}
coolFx's arguments: [1]

It turns out that it doesn’t matter which function calls firstApply, because the only function that is going to be executed is secondApply’s. firstApply’s original context is irrelevant (imagine it as behaving as a static method). It’s just there to facilitate this process by helping “promote” the first parameter (the function) into the executing function itself.

Maybe its a little clearer if you remove secondApply from the equation:

This is the firstApply's this: Empty()
This is the firstApply's first parameter: coolFx()
This is the firstApply's second parameter: [1, Array[1]]

So there you have it. And after all this trouble, I still don’t actually know why Jasmine uses this idiom. Instead of

Function.prototype.apply.apply(self.timer.clearTimeout, [
  j$.getGlobal(),
  [timeoutId],
]);

It seems the following is equivalent:

self.time.clearTimeout.apply(j$.getGlobal(), [timeoutId]);

Bonus

What happens if we chain more than 2 applys together?

Function.prototype.firstApply.secondApply.thirdApply(coolFx, [
  anything,
  [anything],
]);
This is the thirdApply's this: Function.secondApply(x, y)
This is the thirdApply's first parameter: coolFx()
This is the thirdApply's second parameter: [1, Array[1]]

This is the secondApply's this: coolFx()
This is the secondApply's first parameter: 1
This is the secondApply's second parameter: [1]

My cool function is running!!
coolFx's this: Number {[[PrimitiveValue]]: 1}
coolFx's arguments: [1]

Looks like firstApply doesn’t even get run at all. This is puzzling at first, but makes sense when you think about it - secondApply’s original this, firstApply, has been “diverted” to coolFx (remember when I said above that firstApply’s original context is irrelevant?) Thus, when secondApply does its job, its coolFx that it executes. After its done, firstApply is all but forgotten. Of course, this remains the same regardless of how many applys you chain.

More Bonus

So you’ve decided against your better self to use this idiom in your code, but you really don’t like how you have nest the eventual arguments in another array. There’s actually a way to work around that - use call1 instead!

randomFunction.call.secondApply(coolFx, [
  anything,
  anything,
  anything,
  anything,
  anything,
]);
This is secondApply's this:  call()
This is secondApply's first parameter: coolFx()
This is secondApply's second parameter: [1, 1, 1, 1, 1]

My cool function is running!!
coolFx's this: Number {[[PrimitiveValue]]: 1}
coolFx's arguments: [1, 1, 1, 1]

(Okay, this post turned out to be way longer than I had anticipated.)

Footnotes

  1. In case you’re too lazy to follow through on the link, I’ll paste it here for your convenience, courtesy of MDN:

    While the syntax of this function is almost identical to that of apply(), the fundamental difference is that call() accepts an argument list, while apply() accepts a single array of arguments.