Итерация с обратными/анонимными функциями

Я новичок в Node.JS и продвинутом Javascript в целом, но я пытаюсь создать приложение диспетчера расписания самостоятельно, и столкнулся с проблемой (о ней я расскажу позже) при попытке выполнить следующий код:

router.get('/', function (req, res) {
var day = new Date(req.query.day);
Location.getLocations(function (err, locations) {
    if (locations.length > 0) {
        var i;
        for (i = 0; i < locations.length; i++) {
            var location = locations[i];
            Appointment.getAppointments(day, location, function (err, appointments) {
                if (err) throw err;
                if (appointments.length == 0) {
                    // CREATE APPOINTMENTS
                    for (var j = location.available_time_start; j <= location.available_time_end; j += location.appointment_duration) {
                        var newAppointment = new Appointment();

                        newAppointment.start_date = new Date(day.getFullYear(), day.getMonth() + 1, day.getDate(), j);
                        newAppointment.appointment_duration = location.appointment_duration;
                        newAppointment.location = location.id;
                        newAppointment.booked = false;
                        newAppointment.locked = false;

                        Appointment.createAppointment(newAppointment, function (err, appointment) {
                            if (err) throw err;
                            console.log(appointment.location + ' - ' + appointment.start_date);
                        });
                    }
                }
            });
        }
    } else {
        // THERE ARE NO LOCATIONS
    }

    res.render('appointments', { locations: locations });
});

Проблема в следующем:
Когда я пытаюсь выполнить итерацию объекта locations, а затем выполнить функцию getAppointments, код не выполняется именно в этот момент. Позже, когда он выполняется, объект location всегда остается одним и тем же (итерация не работает), что приводит к неожиданному результату (все встречи с одним и тем же/последним местоположением).

Я попытался использовать IIFE (выражение функции с немедленным вызовом) для мгновенного выполнения кода, но когда я сделал это, я не смог получить объект обратного вызова appointments, и моя логика тоже сломалась.

Заранее спасибо!


person Adalberto Ferreira    schedule 29.04.2018    source источник
comment
См. этот ответ для получения дополнительной информации о обратных вызовах и циклах.   -  person Meghan    schedule 29.04.2018
comment
На самом деле я видел это раньше, но, похоже, это не относится к моему делу. Не могли бы вы быть более конкретными?   -  person Adalberto Ferreira    schedule 29.04.2018
comment
используйте let вместо var, скорее всего, это решит ваши проблемы   -  person Jaromanda X    schedule 29.04.2018
comment
Спасибо, это работает! По совпадению, я читал об let в этом посте, когда вы прокомментировали: hacks.mozilla.org/2015/07/es6-in-depth-let-and-const   -  person Adalberto Ferreira    schedule 29.04.2018
comment
Возможный дубликат закрытия JavaScript внутри циклов — простой практический пример   -  person Snow    schedule 04.07.2019


Ответы (2)


Проблема была решена путем использования let вместо var, как предложил @JaromandaX.

person Adalberto Ferreira    schedule 29.04.2018

Ваш код, похоже, сохраняет встречи, но ничего не делает с сохраненными встречами (вы меняете местоположения?).

Когда сохранение встречи идет не так, запросчик не знает об этом, потому что createAppointment является асинхронным, и к тому времени, когда обратный вызов вызывается, res.render('appointments', { locations: locations }); уже выполняется.

Вы можете попробовать преобразовать свои функции обратного вызова в обещания:

const asPromise = (fn,...args) =>
  new Promise(
    (resolve,reject)=>
      fn.apply(undefined,
        args.concat(//assuming one value to resole
          (err,result)=>(err)?reject(err):resolve(result)
        )
      )
  );
const savedAppointmentsForLocation = (day,location,appointments) => {
  const savedAppointments = [];
  if (appointments.length == 0) {
    // CREATE APPOINTMENTS
    for (var j = location.available_time_start; j <= location.available_time_end; j += location.appointment_duration) {
      var newAppointment = new Appointment();
      newAppointment.start_date = new Date(day.getFullYear(), day.getMonth() + 1, day.getDate(), j);
      newAppointment.appointment_duration = location.appointment_duration;
      newAppointment.location = location.id;
      newAppointment.booked = false;
      newAppointment.locked = false;
      savedAppointments.push(
        asPromise(
          Appointment.createAppointment.bind(Appointment),
          newAppointment
        )
      );
    }
  }
  //you are not doing anything with the result of the saved appointment
  //  I'll save it as promise to see if something went wrong to indicate
  //  to the requestor of the api that something went wrong
  return Promise.all(savedAppointments);
}

router.get('/', function (req, res) {
  var day = new Date(req.query.day);
  asPromise(Location.getLocations.bind(Location))
  .then(
    locations=>
      promise.all(
        locations.map(
          location=>
            asPromise(Appointment.getAppointments.bind(Appointment),[day,location])
            .then(appointments=>[location,appointments])
        )
      )
  )
  .then(
    results=>//results should be [ [location,[appointment,appointment]],...]
      Promise.all(
        results.map(
          ([location,appointments])=>
            savedAppointmentsForLocation(day,location,appointments)
            .then(ignoredSavedAppointment=>location)
        )
      )
  )
  .then(locations=>res.render('appointments', { locations: locations }))
  .catch(
    error=>{
      console.log("something went wrong:",error);
      res.status(500).send("Error in code");
    }
  )
});
person HMR    schedule 29.04.2018
comment
После вашего комментария я много изучил промисы и переработал свой код, чтобы использовать их. Теперь у меня гораздо больше контроля над решением. Спасибо! - person Adalberto Ferreira; 01.05.2018