15

So I get that an array of [200,599] is returned from the promise and the callback function inside spread is being passed into Function.apply.bind, but now I'm lost. How is the array of [200,599] split into x and y? How exactly does the apply.bind work?

function getY(x) {
        return new Promise( function(resolve,reject){
            setTimeout( function(){
                resolve( (3 * x) - 1 );
            }, 100 );
        } );
    }

function foo(bar,baz) {
    var x = bar * baz;

    // return both promises
    return [
        Promise.resolve( x ),
        getY( x )
    ];
}

function spread(fn) {
    return Function.apply.bind( fn, null );
}

Promise.all(
    foo( 10, 20 )
)
.then(
    spread( function(x,y){
        console.log( x, y );    // 200 599
    } )
)
Gwater17
  • 2,018
  • 2
  • 19
  • 38

4 Answers4

14

.apply() is a method on function objects. Like so:

console.log(Math.max.apply(null, [1, 2, 3])); // 3

.apply() accepts two arguments, the context (what would become this inside of the target function) and an iterable of arguments (usually an array, but the arguments array like also works).


.bind() is a method on function objects. Like so:

const x = {
  foo: function() {
    console.log(this.x);
  },
  x: 42
}

var myFoo = x.foo.bind({x: 5});

x.foo(); // 42
myFoo(); // 5

.bind() takes a context (what would become this), and optionally, additional arguments, and returns a new function, with the context bound, and the additional arguments locked


Since .apply() is a function in on itself, it can be bound with .bind(), like so:

Function.prototype.apply.bind(fn, null);

Meaning that the this of .apply() would be fn and the first argument to .apply() would be null. Meaning that it would look like this:

fn.apply(null, args)

Which would spread the parameters from an array.

Graham
  • 7,431
  • 18
  • 59
  • 84
Madara's Ghost
  • 172,118
  • 50
  • 264
  • 308
9

Spread takes a function and binds the apply method to, partially applying the null argument. So in short,

spread(fn)

is transformed to

args => fn.apply(null, args)

which is the same as using the ES6 spread syntax

args => fn(...args)

where the function got its name from.


If you want the long answer, remember what bind does:

method.bind(context, ...args1)

returns a function that works like

(...args2) => method.call(context, ...args1, ...args2)

In our case, method is apply, the context is fn and the first arguments are null, so the call

Function.apply.bind( fn, null )

will create a function that works like

(...args2) => (Function.apply).call(fn, null, ...args2)

which is equivalent to the fn.apply(…) call above, given that apply is the method inherited from Function.prototype in both accesses.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Good explanation, i undestood that this fix the first ```apply``` argument to ```null``` but why we need to do that, is there any real useful situations that this may be helpful!! – Saher Elgendy Nov 10 '19 at 18:44
  • 1
    @SaherElgendy Not sure what you're asking. `apply` needs a first argument, and what else would you pass? If you are asking about the helpfulness of the `spread` function, that's mostly deprecated as of ES6 rest/spread syntax. – Bergi Nov 10 '19 at 18:50
  • You mean that this part ```Function.apply.bind( fn, null )``` is just an old way to express spread syntax? – Saher Elgendy Nov 10 '19 at 19:06
  • 1
    @SaherElgendy `spread` is a higher-order function, basically a decorator, around the called function. Instead of writing `spread(function(x,y) { … })` we would just use `function([x, y]) { … }`. – Bergi Nov 10 '19 at 19:11
  • my problem here is that i can't understand the importance of that part ```Function.apply.bind( fn, null )``` in the code, i know what it does but what can be a practical use of it? – Saher Elgendy Nov 10 '19 at 19:20
  • @SaherElgendy You've seen where and how `spread` is used, right? Well, if that code wasn't there, it wouldn't work. – Bergi Nov 10 '19 at 19:23
  • yes it would by just removing ```spread``` function totally from code and then we get an array which we can convert it by any other mean! – Saher Elgendy Nov 10 '19 at 19:32
  • 1
    Sure, but `spread(function(x,y) { … })` is much more "practical" than `function(arr) { var x = arr[0], y = arr[1]; … }`. – Bergi Nov 10 '19 at 19:34
  • i think i answered my self, this is why this part of code used to spread the results – Saher Elgendy Nov 10 '19 at 19:35
  • i understand now, thank you your answer is great, thanks for help – Saher Elgendy Nov 10 '19 at 19:35
3

The spread function is just a utility function to convert an array, into parameters passed to a function. The apply is doing the converting, and the bind is binding it to the calling function so that the "this" context is connected to same function.

To see how spread is working in a simpler form ->

spread(function (x,y){console.log(x,y);})([1,2])

You will get the answer, 1 2, as 1 is passed to x, and 2 is passed to y.

So in your example your promise.all is returning an array of resolved promises. These are then getting mapped to parameters to your function(x,y).

Keith
  • 22,005
  • 2
  • 27
  • 44
1

The reason it works is the "destructuring" nature of apply (if given an array of values, they would be provided spreaded to the function you use apply on).

Now back to your code when calling bind on apply, the value returned is a function which returns the same function provided to bind, the only thing different is when executed it would be called using apply (with an array as thisArg in your case), but it isn't going to be executed until you call it. In your case when the promise has resolved, the function provided tothen woule be executed with an array of arguments provided by Promise resolution.

function spread(fn){
   let boundedFn = fn.bind(fn)

   return function(arg){
      return boundedFn.apply(null,arg)
   }
}
   
spread((x, y, c) => console.log(x, y, c))([1,2,3])

// or

Promise.resolve([6, 5]).then(spread((a, b) => console.log(a, b)))

The reason bind is provided (in your code) with null as second param is to prevent the array provided by the caller from being given to apply as its first param, which reserved for this.

Rafi Henig
  • 5,950
  • 2
  • 16
  • 36