promise.all不等待Firestore查询循环

时间:2019-03-30 09:39:53

标签: javascript node.js firebase promise google-cloud-firestore

我正在将此代码从实时数据库转换为Firestore。

为了创建一些待稍后处理的作业,该代码循环通过Firestore中的每个用户(doc),然后循环通过每个用户内部2个嵌套子集合的每个文档。

我希望函数在完成每个查询之前先等待它完成。 Promise.all()总是在添加了3个诺言之后触发,其中第一个未定义。

我曾尝试使用async / await,但这并不是目标。 我尝试为最嵌套的逻辑(writeJobToBackLog())创建一个单独的promises数组。 两者都没有成功。

经过数小时的玩耍,我什至仍然不了解发生了什么,我的伐木技能可能使我无法获得更清晰的画面。

我不是一个有承诺的专业人士,但是我已经与他们做过一些合作,主要是通过实时数据库进行的。


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逻辑正常。 我希望它为每个“写入”操作使用一个返回的promise填充promise数组,然后等待所有写入成功。

根本不向数组添加任何承诺。

感谢您的帮助!


所以我更改了代码:

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 个答案:

答案 0 :(得分:2)

您可以尝试一下。我删除了嵌套的Promise,现在使用了Promise链。

您必须自己添加错误处理代码。

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

}

答案 1 :(得分:0)

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

答案 2 :(得分:0)

我会将逻辑拆分为几个较小的函数(更易于跟踪,测试和调试-BTW,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
   };
}