Асинхронная операция findOne () внутри цикла forEach

1

Мне трудно понять обещания JavaScript. Я ищу свои модели Mongoose для объектов, которые удовлетворяют определенному условию, и если они существуют, я хочу превратить объект в простой объект JS и добавить на него свойство.

К сожалению, я не могу оборачивать голову тем, как я могу обеспечить, чтобы мой цикл forEach выполнялся полностью до того, как мое обещание закончится. Посмотрите мой код.

// Called to check whether a user has participated in a given list of challenges
participationSchema.statics.getParticipation = function(user, challenges) {
  return new Promise((resolve, reject) => {
    challengesArray = [];
    challenges.forEach((challenge) => {
      // Model#findOne() is Async--how to ensure all these complete before promise is resolved?
      Participation.findOne({user, challenge})
      .then((res) => {
        if (res) {
          var leanObj = challenge.toObject();
          leanObj.participation = true;
          challengesArray.push(leanObj);
        }
      })
      .catch(e => reject(e));
    })
    console.log("CHALLENGES ARRAY", challengesArray); // Challenges Array empty :(
    resolve(challengesArray);
  });
}

Я просмотрел похожие вопросы, но не могу ответить. Цените помощь.

Теги:
mongoose
asynchronous
promise

1 ответ

1
Лучший ответ

Итак, что происходит, когда вы вызываете getParticipation это то, что цикл forEach выполняется полностью, и все индивидуальные обещания для Participation.findOne созданы, но еще не решены. Выполнение не дожидается их разрешения и продолжается после forEach, разрешая обещание верхнего уровня challengesArray, которое на данный момент еще пусто. Спустя некоторое время обещания, созданные в forEach начинают разрешаться, но их результаты теряются.

Кроме того, как отметил Берги в комментариях, вложение обещаний считается анти-паттерном; обещания должны быть прикованы, а не вложены.

Вам нужно использовать что-то вроде Promise.all чтобы Promise.all дождаться Promise.all всех ваших обещаний, затем отфильтровать все несуществующие результаты и, наконец, вернуть массив.

participationSchema.statics.getParticipation = function(user, challenges) {
  return Promise.all(challenges.map(challenge => {
    return Participation.findOne({user, challenge}).then(result => {
      if (result) {
        var leanObj = challenge.toObject();
        leanObj.participation = true;
        return leanObj;
      }
    });
  })
  // at this point, results contains an array of 'leanObject' and 'undefined' depending if the 'findOne' call returned anything and the code withing the 'if' above was run
  .then((results) => {
    return results.filter(result => !!result) // filter out 'undefined' results so we only end up with lean objects
  });
}
  • 0
    Ах, это так интересно! Таким образом, в этом проекте вы готовите все обещания в массиве, и promises.all() гарантирует, что они все завершены, правильно? Пожалуйста, дайте мне попробовать это очень быстро и вернемся к вам.
  • 1
    Не уверен, что вы имеете в виду под подготовкой. Происходит то, что сначала массив задач преобразуется в массив обещаний, которые представляют собой вызовы Participation.findOne и все запросы выполняются, в основном, параллельно. Затем эти запросы начинают выполняться в некотором произвольном порядке, и обещания начинают разрешаться соответствующим образом, который запускает условие then для каждого из них и проверяет, есть ли результаты, преобразуя их в leanObj если это так. Как только все эти обещания разрешены, Promise.all разрешается с массивом результатов, полученных по каждому обещанию.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню