Promise.all не ждет цикла запроса Firestore - PullRequest
0 голосов
/ 30 марта 2019

Я конвертирую этот код из базы данных реального времени в Firestore.

Для создания некоторых заданий, которые будут обрабатываться позже, код проходит через каждого пользователя (документа) в Firestore, а затем через каждый документ из 2 вложенных подколлекций внутри каждого пользователя.

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

Я пытался использовать async / await, но в любом случае это не цель. Я попытался создать отдельный массив обещаний только для самой вложенной логики (writeJobToBackLog ()). Оба без успеха.

После нескольких часов игры я все еще не понимаю, что происходит, и мои навыки ведения журнала, вероятно, мешают мне получить более четкую картину.

Я не профессионал с обещаниями, но я немного поработал с ними, в основном с базой данных реального времени.


var database = admin.firestore();

// prepare()

test();

function test() {
  console.log("prepare() called ...");

  let promises = [];

    database
      .collection("users")
      .get()
      .then((snapshot) => {
        snapshot.forEach((user) => {
          user = user.data();
          const userId = user.userId;

            database
              .collection("users")
              .doc(userId)
              .collection("projects")
              .get()
              .then((snapshot) => {
                snapshot.forEach((project) => {
                  project = project.data();
                  const projectUrl = project.projectUrl;
                  const projectId = project.projectId;

                    database
                      .collection("users")
                      .doc(userId)
                      .collection("projects")
                      .doc(projectId)
                      .collection("subProjects")
                      .get()
                      .then((snapshot) => {
                        snapshot.forEach((subProject) => {

                          subProject.keywords.map(async (keyword) => {
                            let unreadyJob = {
                              keyword: keyword,
                            };

                            // returns a promise
                            let write = writeJobsToBackLog(unreadyJob);
                            writePromises.push(write);
                            return null;
                          });
                          return;
                        });
                        return;
                      })
                      .catch((error) => {
                        console.log(error);
                      })
                  return;
                });
                return;
              })
              .catch((error) => {
                console.log(error);
              })
        });
        return;
      })
      .catch((error) => {
        console.log(error);
      })
  Promise.all(promises)
    .then(() => {
      console.log("prepare() finished successfully..." +
          promises.map((promise) => {
            console.log(promise);
            return null;
          }));
      return null;
    })
    .catch((error) => {
      console.log("prepare() finished with error: " + error + "...");
      return null;
    });
}
function writeJobsToBackLog(unreadyJob) {
  console.log("writing job to Backlog ...");
  return database
    .collection("backLog")
    .doc()
    .set(unreadyJob);
}

Вот что выводится на консоль:

prepare() called ...
prepare() finished successfully...
writing job to Backlog ...
writing job to Backlog ...
writing job to Backlog ...
writing job to Backlog ...
(... more of those ...)

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

Никакие обещания не добавляются в массив вообще.

Спасибо за любую помощь!


Итак, я изменил код:

async function test() {
  console.log("prepare() called ...");

  const users = await database.collection("users").get();
  users.forEach(async (user) => {
    const userId = user.data().userId;
    const projects = await database
      .collection("users")
      .doc(userId)
      .collection("projects")
      .get();

    projects.forEach(async (project) => {
      const projectUrl = project.data().projectUrl;
      const projectId = project.data().projectId;
      const subProjects = await database
        .collection("users")
        .doc(userId)
        .collection("projects")
        .doc(projectId)
        .collection("subProjects")
        .get();

      subProjects.forEach(async (subProject) => {

        subProject.data().keywords.map(async (keyword) => {
          let unreadyJob = {
            keyword: keyword,
          };
          await writeJobsToBackLog(unreadyJob);
        });
      });
    });
  });
  console.log("finished");
}

function writeJobsToBackLog(unreadyJob) {
  console.log("writing job to Backlog ...");
  return database
    .collection("backLog")
    .doc()
    .set(unreadyJob);
}

Это дает тот же результат:

prepare() called ...
finished
writing job to Backlog ...
writing job to Backlog ...
writing job to Backlog ...
...

Что я делаю не так. Спасибо!

Ответы [ 3 ]

2 голосов
/ 30 марта 2019

Вы можете попробовать это.Я удалил вложенное обещание, и теперь оно использует цепочку обещаний.

Вы должны добавить код обработки ошибок самостоятельно.

let users = await database.collection("users").get();

let userPromises = [];
users.forEach((userDoc) => {
    let userDocData = userDoc.data();
    let userId = userDocData.userId;

    // Create promises for each user to retrieve sub projects and do further operation on them.
    let perUserPromise = database.collection("users").doc(userId).collection("projects").get().then((projects) => {

        // For every project, get the project Id and use it to retrieve the sub project.
        let getSubProjectsPromises = [];
        projects.forEach((projDoc) => {
            const projectId = projDoc.data().projectId;
            getSubProjectsPromises.push(database.collection("users").doc(userId).collection("projects").doc(projectId).collection("subProjects").get());
        });

        // Resolve and pass result to the following then()
        return Promise.all(getSubProjectsPromises);

    }).then((subProjectSnapshots) => {

        let subProjectPromises = [];
        subProjectSnapshots.forEach((subProjSnapshot) => {
            subProjSnapshot.forEach((subProjDoc) => {

                // For every sub project, retrieve "keywords" field and write each keyword to backlog.
                const subProjData = subProjDoc.data();
                subProjectPromises.push(subProjData.keywords.map((keyword) => {
                    let unreadyJob = {
                        keyword: keyword,
                    };
                    return writeJobsToBackLog(unreadyJob);
                }));
            });
        });

        return Promise.all(subProjectPromises);
    });

    userPromises.push(perUserPromise);
});

// Start the operation and wait for results
await Promise.all(userPromises);

}

0 голосов
/ 30 марта 2019

Я бы разбил логику на несколько более мелких функций (способ проще следовать, проверить и отладить - кстати, для чего нужен projectUrl?) И написать что-то вроде этого:

async function getUsers() {
   const users = await database.collection("users").get();
   const userIds = users.map(user => user.data().userId);
   const projectPromises = userIds.map(getUserProjects);

   const projects = await Promise.all(projectPromises);
   return projects;
}

async function getUserProjects(userId) {
   const projects = await database
      .collection("users")
      .doc(userId)
      .collection("projects")
      .get()
      .map(getUrlAndId);

   const subprojectPromises = projects.map(({
      projectId
   }) => getUserSubprojects(userId, projectId));

   const subprojects = await subprojectPromises;
   return subprojects;
}

function getUserSubprojects(userId, projectId) {
   const subProjects = await database
      .collection("users")
      .doc(userId)
      .collection("projects")
      .doc(projectId)
      .collection("subProjects")
      .get();

   const keywordJobPromises = subprojects.map(keywordJobs);
   return Promise.all(keywordJobPromises);
}

function keywordJobs = (subproject) {
   const keywordPromises = subproject.keywords.map((keyword) => {
      const unreadyJob = {
         keyword
      };
      return writeJobsToBackLog(unreadyJob);
   });

   // Running out of good variable names here...
   const keyword = await Promise.all(keywordPromises); 
   return keyword;
}

function getUrlAndId(project) {
   const data = project.data();
   return {
      projectUrl: data.projectUrl,
      projectId: data.projectId
   };
}
0 голосов
/ 30 марта 2019

Как и в ECMAScript8, используйте await для получения результата от Promise

const users = await database.collection("users").get();
users.forEach(async (user) => {
    const userId = user.data().userId;
    const projects = await database.collection("users").doc(userId).collection("projects").get();
    ....
});
...