2

When passing a callback, is doing so by value:

utility.subscribe(this.callback);

not the same as passing it as its expanded form?

utility.subscribe(arg => this.callback(arg));

I had the understanding that they are equivalent, but encountered a problem when context was needed to be passed together with the callback.

var utility = {
  name: 'utility',
  subscribe(callback) {
    var msgForCallback = 'Hey callback!';
    callback(msgForCallback);
  }
}

var user = {
  name: 'user',
  useSubscriber() {
    utility.subscribe(
      this.callback
    );
  },
  useSubscriberWithArrow() {
    utility.subscribe(
      arg => this.callback(arg)
    );
  },
  callback (arg) {
    console.log(`Context: ${this.name}`);
    console.log(`The utility says: ${arg}`);
  }
}

console.log('\nCall user.useSubscriber();');
user.useSubscriber();
console.log('\nCall user.useSubscriberWithArrow();');
user.useSubscriberWithArrow();

I realize that the callback function is a regular function, and that's why the context may be lost. So I tried changing it to an arrow function, but then even the call with an expanded callback loses the context.

callback: (arg) => { .. }

instead of

callback (arg) { .. }

var utility = {
  name: 'utility',
  subscribe(callback) {
    var msgForCallback = 'Hey callback!';
    callback(msgForCallback);
  }
}

var user = {
  name: 'user',
  useSubscriber() {
    utility.subscribe(
      this.callback
    );
  },
  useSubscriberWithArrow() {
    utility.subscribe(
      arg => this.callback(arg)
    );
  },
  callback: (arg) => {
    console.log(`Context: ${this.name}`);
    console.log(`The utility says: ${arg}`);
  }
}

console.log('\nCall user.useSubscriber();');
user.useSubscriber();
console.log('\nCall user.useSubscriberWithArrow();');
user.useSubscriberWithArrow();
Andrew
  • 41
  • 5
  • 1
    Does this answer your question? [Are 'Arrow Functions' and 'Functions' equivalent / exchangeable?](https://stackoverflow.com/questions/34361379/are-arrow-functions-and-functions-equivalent-exchangeable) – Always Helping Aug 16 '20 at 05:49
  • @AlwaysHelping I guess it is a slightly different question, as I'm also wondering how the code reads/expands the function passed, as a regular function or as an arrow function? – Andrew Aug 16 '20 at 06:13
  • @Andrew, I've described what is the problem, why this problem occurs...read my answer...everything will be clear to you.... – reyad Aug 16 '20 at 07:12

2 Answers2

2
  1. To understand what is wrong with your code, you first must understand the difference between regular function declaration and arrow function declaration. arrow function's this is bind to it's environments this(i.e. where it is defined).

  2. secondly, you've to understand that, function within a object method does not point to 'this' of the object, rather it's point to global this i.e. like below:

let obj = {
  someMethod() {
    function someFun() {
      // here 'this' points to 'global this'
      // i.e. 'this' does not points to obj
    }
  }
}

I guess the above points are clear...

Now, come to your code:

First rewrite callback as a regular function. don't write it as arrow.

I'm providing the code below with some more lines(for debugging purposes):

let utility = {
  name: 'utility',  
  subscribe(callback) {
    let msgForCallback = 'Hey callback!';
    callback(msgForCallback);
  }
};


let user = {
  name: 'user',

  useSubscriber() {
    utility.subscribe(this.callback);
  },

  useSubscriberWithArrow() {
    utility.subscribe(arg => this.callback(arg + " With Arrow Huh "));
  },

  callback(arg) {
    console.log('this: ');
    console.log(this);
    console.log("-----------");
    console.log(`Context: ${this.name}`);
    console.log(`The utility says: ${arg}`);
    console.log("__________________________________________________________________________");
  }
};


console.log('\nCall user.useSubscriber();');
user.useSubscriber();

console.log('\nCall user.useSubscriberWithArrow();');
user.useSubscriberWithArrow();

We'll be talking about what is wrong with the code given above.

The wrong with the code is user.useSubscriber() won't be able to resolve this.name when callback is called.

Why?

Note: user.callback() is not an arrow function, rather it's a regular function and is passed to utility.subscribe(). So, it becomes, like the second point I've described above, function with in object method, so, the this will be bind to global this. You may prove it by running the code. the code's output is somewhat like below(I've tested in node v10.16.*):

Call user.useSubscriber();
this:
Object [global] {
  DTRACE_NET_SERVER_CONNECTION: [Function],
  DTRACE_NET_STREAM_END: [Function],
  DTRACE_HTTP_SERVER_REQUEST: [Function],
  DTRACE_HTTP_SERVER_RESPONSE: [Function],
...
...
-----------
Context: undefined
The utility says: Hey callback!
__________________________________________________________________________

Call user.useSubscriberWithArrow();
this:
{ name: 'user',
  useSubscriber: [Function: useSubscriber],
  useSubscriberWithArrow: [Function: useSubscriberWithArrow],
  callback: [Function: callback] }
-----------
Context: user
The utility says: Hey callback! With Arrow Huh
__________________________________________________________________________

I guess, that clears the problem.

Now, why user.useSubscriberWithArrow() works:

It works cause, remember what I've discussed in the first point - arrow function is bind to it's environments 'this'(i.e. where it is defined). So, you've passed a arrow function as callback, not user.callback() itself, and that arrow function is used to call this.callback(). And, the arrow's this is bound to user, cause it is defined within user object.

I guess that clears your problem. If you've anymore questions ,ask me in the comment...

FZs
  • 16,581
  • 13
  • 41
  • 50
reyad
  • 1,392
  • 2
  • 7
  • 12
  • Why must `callback` be a regular function? If I define it as an arrow function (before passing it into `subscribe`), shouldn't it get bound to the object's context? – Andrew Sep 06 '20 at 13:03
0

I believe I have my own answers. For the first part:

utility.subscribe(this.callback);

is equivalent to:

utility.subscribe(function (arg) {
  console.log(`Context: ${this.name}`);
  console.log(`The utility says: ${arg}`);
});

and not:

utility.subscribe(arg => {
  console.log(`Context: ${this.name}`);
  console.log(`The utility says: ${arg}`);
});

As for the second part, the change would work if the user object were to be a class:

var utility = {
  name: 'utility',
  subscribe(callback) {
    var msgForCallback = 'Hey callback!';
    callback(msgForCallback);
  }
}

var User = class {

  constructor() {
    this.name = 'user';
  }

  useSubscriber() {
    utility.subscribe(
      this.callback
    );
  }

  useSubscriberWithArrow() {
    utility.subscribe(
      arg => this.callback(arg)
    );
  }

  callback = (arg) => {
    console.log(`Context: ${this.name}`);
    console.log(`The utility says: ${arg}`);
  }
}

var user = new User();
console.log('\nCall user.useSubscriber();');
user.useSubscriber();
console.log('\nCall user.useSubscriberWithArrow();');
user.useSubscriberWithArrow();
Andrew
  • 41
  • 5