1

I've been reading quite a bit about the method bind() and I am beginning to understand that it sets this to a specific object. That almost always means that there is this somewhere within the function definition that is pointing to a certain object. However, I've seen in instances where bind() is used without this in the definition of the functions. More specifically, this is used as an argument, which confuses me. For example,

const eventLoader = new DataLoader((eventIds) => {
  return events(eventIds);
});

const events = async eventIds => {
  try {
    const events = await Event.find({ _id: { $in: eventIds } });
    return events.map(event => {
      return transformEvent(event);
    });
  } catch (err) {
    throw err;
  }
};

const user = async userId => {
  try {
    const user = await userLoader.load(userId.toString());
    return {
      ...user._doc,
      _id: user.id,
      createdEvents: eventLoader.load.bind(this, user._doc.createdEvents)
    };
  } catch (err) {
    throw err;
  }
};

In this example, eventLoader.load.bind(this, user._doc.createdEvents) uses this as an argument for bind() even though the eventLoader function nor the events function have this in their function definition. Isn't the first argument of bind() where you want the pre-existing this to point towards?

Kevvv
  • 3,655
  • 10
  • 44
  • 90
  • 3
    `eventLoader` is not a function. You'd have to look at the source of `eventLoader.load` (and if that's just the function passed to the constructor, then you are right, it's useless). However, given the call to `.bind` is inside an arrow function leads me to believe that the author just wanted to provide an arbitrary value and chose this. `null` would have been better IMO. Or they don't know what they are doing... – Felix Kling Apr 28 '19 at 19:35
  • Could you explain further on the point about whether the source of eventLoader.load is a function passed to the constructor or not? Btw, I see that you work at Facebook. I'm learning to this DataLoader from GraphQL! – Kevvv Apr 28 '19 at 19:40
  • 4
    No, [`DataLoader.load`](https://github.com/graphql/dataloader/blob/master/src/index.js) needs `this` to be the DataLoader. It should really be `eventLoader.load.bind(eventLoader, ...)` the code shown is wrong. – Jonas Wilms Apr 28 '19 at 19:50
  • 1
    Ah. I don't know anything about `DataLoader` though :D You where saying that *"even though the eventLoader function nor the events function have this in their function definition"*. But the definition of these functions is irrelevant since `.bind` is called on `eventLoader.load`. So unless `DataLoader` is defined as `function DataLoader(onload) { this.load = onload; }`, which would make `((eventIds) => { return events(eventIds); }` the `eventLoader.load` function, looking at the functions in this code example is irrelevant (and you should look at the source of `eventLoader.load` instead). – Felix Kling Apr 28 '19 at 19:50

2 Answers2

2

Isn't the first argument of bind() where you want the pre-existing this to point towards?

Yes, exactly.

In this example, eventLoader.load.bind(this, user._doc.createdEvents) uses this as an argument for bind() even though the eventLoader function nor the events function have this in their function definition.

To be more precise, DataLoader.load needs this to be the DataLoader to work correctly. Therefore it would make sense to .bind the eventLoader to it eventLoader.bind(eventLoader, ...)

Binding this makes no sense, as that is window (as an arrow function takes the context of the parent function ², and as you have no parent function [from the code shown] the parent is the global scope).

¹ Read on

² Even more to read

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • How does the other DataLoader userLoader.load(userId.toString()) work without using the bind() method? – Kevvv Apr 28 '19 at 20:08
  • 2
    @kevv cause `a.b()` calls `b` with `this` being `a`. Thats basically how `this` works (as long as you don't `.bind` and such stuff) – Jonas Wilms Apr 28 '19 at 20:10
0

The first argument of the bind function sets the this value when the function is called.

Example:

// You have an object
var obj = { test: "Yeah, test!" };
// We check the object properties
console.log(obj.test);

// We define a function to work with an argument
function check(myobj) {
    console.log("Is the same object?", obj === myobj);
    console.log("Is the same object?", myobj.test);
}

// Now we define a function that will work with an object as "this"
function checkthis() {
    // Notice how I use the "this" keyword, which will be the one that inherits or the one that you set
    console.log("Is the same object?", obj === this);
    console.log("Is the same object?", this.test);
}

// We call the first check
check(obj);

// Now we call the second checkthis but with another "this" context
// The first argument of call() will set the "this" context of the function and then call it instantly.
checkthis.call(obj);

// And now we are going to save another function with the this context changed.
// The first argument of bind() will also set the "this" context, but will save a reference of the function for later use, instead of calling it.
var newcheckthis = checkthis.bind(obj);

// Now we call this new function.
newcheckthis();

Note that newcheckthis is the checkthis function but with the this context changed. checkthis will keep its context.

Also, with bind you can force to create a function reference with the this. context changed AND with default arguments set.

Example:

// We create a function that will sum the some this object value to an argument
function sum(sumvalue) {
    return this.value + sumvalue;
}

// We create the main object
var obj = { value: 10 };

// Then we call the function setting the "this" context and passing an argument
// Note how I pass obj as the first argument and 5 as the second
// Actually it will set as the first argument (sumvalue) the second argument in the call, as the first argument in the call will be set to the "this" context. 
var result = sum.call(obj, 5);

// And we can use the same trick with bind()
var newsum = sum.bind(obj, 5);

// Now we have a newsum  function that has the this context set to "obj" and the first argument (sumvalue) set to 5 by default, which cannot be replaced.
// If we cann the function, it will di the same as the call() function above
result = newsum();

// result will still be 15.

So, call() and bind() (there's another one: apply(), but works a bit different) will have this signature:

.bind/.call(THISCONTEXT, THIS_2ND_ARGUMENT_WILL_BE_THE_FIRST_ONE, 3RD_AS_SECOND, AND_SO_ON...);
Jorge Fuentes González
  • 11,568
  • 4
  • 44
  • 64