Привязка JavaScript (this) без доступа к классу

#javascript #callback #node-cron

#javascript #обратный вызов #узел-cron

Вопрос:

У меня возникли проблемы с доступом к методам класса без каких-либо изменений в самом классе.

Смотрите следующий демонстрационный код:

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

class hiddenClass {

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

hiddenClass.hiddenFunction(myCallback);
  

В этом простом примере я хочу получить доступ к this.otherMethod() в моем обратном вызове. Очевидный ответ — изменить callback() на callback.bind(this)() , однако в этом случае hiddenClass будет библиотекой.

Как я могу вызвать этот другой метод?

Для справки, я пытаюсь создавать задания cron с использованием node-cron. Я хочу уничтожить эти задания, если проверка базы данных возвращает true, проверяя каждый цикл задания (в обратном вызове). Задание имеет внутренний метод .destroy() . Я знаю, что могу сохранить задание cron в переменной, а затем вызвать variable.destroy() , но есть ли способ сделать это в обратном вызове (поскольку эти задания создаются в цикле for, и я не хочу точно определять, какое из них уничтожить внутри обратного вызова)

 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)
}
  

);

Ответ №1:

Ваши рассуждения верны, вам нужно будет привязать обратный вызов к рассматриваемому классу. Однако это можно было бы сделать в вашем примере кода, изменив вызов hiddenFunction:

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

Это не потребует внесения изменений в библиотеку.


Что касается вашего ссылочного кода…

До ES2015 способ сделать это заключался бы в создании анонимных функций, связанных вызовом со значением текущей итерации цикла, для простого примера:

 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
*/
  

ОДНАКО в вашем коде это было бы невозможно, поскольку вы пытаетесь передать анонимный обратный вызов и ссылаться на значение итерации текущего цикла в указанной функции, требуя, чтобы анонимная функция знала возвращаемое значение cron.schedule перед передачей в cron.schedule .

Решение проблемы заключается в использовании let или const в вашем цикле для привязки значения текущей итерации. Они ограничены областью блока, а не лексической областью, поэтому они сохраняют значение на каждой итерации, что приводит к N закрытиям одной и той же анонимной функции. Мы можем продемонстрировать разницу между ними в следующем цикле:

 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 }
*/
  

Связывая все это вместе, ваш планировщик циклов будет выглядеть следующим образом:

 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();
  });
}
  

Комментарии:

1. замечательно, что работает, спасибо! Я действительно думал об этом решении, но я бы подумал, me что оно не определено. Моя логика заключается в том, что переменная назначается только после запуска функции cron, почему она назначается в этом случае?

2. Переменная назначается во время cron.schedule вызова, и обратный вызов обязательно всегда вызывается после этого времени.