0

I am having trouble accessing a class' methods without some modifications to the class itself.

See the following demo code:

function myCallback() {
  this.otherMethod();
}

class hiddenClass {

  static hiddenFunction(callback) {
    callback();
  }
  
  static otherMethod(){
    console.log("success");
  }
}

hiddenClass.hiddenFunction(myCallback);

In this simple example, I want to access this.otherMethod() in my callback. The obvious answer is to change callback() to callback.bind(this)(), however in this case, hiddenClass would be a library.

How can I call this other method?

For reference, I am trying to create cron jobs using node-cron. I want to destroy these jobs if a database check returns true, checking every cycle of the job (in the callback). The job has an internal method .destroy(). I am aware I can save the cron job in a variable, then call variable.destroy(), but is there a way of doing it in the callback (as these jobs are created in a for loop and I don't want to pinpoint which one to destroy from inside the callback)

cron.schedule(`5 * * * * *`, async function () {
  schedule = await Schedule.findById(schedule._id);
  if (!schedule) this.destroy() // Destroy the job if schedule is not found (this returns Window)
}

);

gjones
  • 3
  • 1

1 Answers1

0

Your reasoning is sound, you would need to bind the callback to the class in question. However, it could be done in your sample code by modifying the call to hiddenFunction:

hiddenClass.hiddenFunction(myCallback.bind(hiddenClass));

This won't require a modification to the library.


As for your reference code...

The pre-ES2015 way of doing this would be to create anonymous functions bound by calling with the current loop iteration's value, for a simple example:

var schedules = [1,2,3,4,5];
for(var i=0;i<schedules.length;i++){
  setTimeout(function(){console.log(schedules[i])}, 10);
}
/* => outputs:
5
5
5
5
5
*/
for(var i=0;i<schedules.length;i++){
  // create an anonymous function with the first arg bound at call time
  (function(j){
    setTimeout(function(){console.log(schedules[j])}, 10);
  }(i));
}
/* => outputs:
1
2
3
4
5
*/

HOWEVER, this would not be possible in your code as you're trying to pass an anonymous callback and reference the current loop's iteration value in said function, requiring the anonymous function to know the return value of cron.schedule before itself being passed to cron.schedule.

The solution to the problem is to use let or const in your loop to bind the current iteration's value. They are block scoped, not lexically scoped, so they retain the value at each iteration resulting in N closures of the same anonymous function. We can demonstrate the difference between the two in the following loop:

for(var foo of [1,2,3,4,5]){
  const bar = foo;
  (async function(){
    await Promise.resolve().then(()=>console.log({bar, foo}));
  }());
}

/* outputs:
{ bar: 1, foo: 5 }
{ bar: 2, foo: 5 }
{ bar: 3, foo: 5 }
{ bar: 4, foo: 5 }
{ bar: 5, foo: 5 }
*/

Tying it all together, your loop scheduler would look like the following:

for(let schedule of schedules){
  const me = cron.schedule(`5 * * * * *`, async function () {
    // the value of `me` is bound to each iteration and you can refer to it as needed
    schedule = await Schedule.findById(schedule._id);
    if (!schedule) me.destroy();
  });
}
gcochard
  • 11,408
  • 1
  • 26
  • 41
  • wonderful that works, thanks! I actually thought of this solution, but I would have thought `me` would be undefined. My logic is that the variable is only assigned after the cron function runs, why is it assigned in this case? – gjones Aug 25 '20 at 17:50
  • The variable is assigned at the time that `cron.schedule` is called, and the callback is necessarily always called _after_ that time. – gcochard Aug 25 '20 at 20:13