【问题标题】:promise.all does not wait for firestore query to looppromise.all 不等待 firestore 查询循环
【发布时间】:2019-05-10 23:17:05
【问题描述】:

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

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

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

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

玩了几个小时后,我什至不明白发生了什么,我的日志记录技能可能使我无法获得更清晰的画面。

我不是 Promise 的专家,但我已经完成了一些工作,主要是使用实时数据库。


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 ...
...

我做错了什么。谢谢!

【问题讨论】:

  • 这是一种非常糟糕的使用 Promise 的方式。使用 Promise 链接而不是嵌套。
  • 感谢您的回答。你能举个例子吗?如果没有“promises.push()”,它们会被链接起来,不是吗?
  • 即使在删除 promise.push() 之后,您也已经嵌套了 firestore 返回的承诺。查看Chaining Promisesthis
  • Promise.all不是用来表示相互独立的promise吗?
  • 谢谢。我如何避免与“forEach”嵌套?我真的不知所措......这对逻辑有何影响?代码可能更扁平/更干净,但仍然在做同样的事情?感谢您的帮助。

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


【解决方案1】:

你可以试试这个。我删除了嵌套的 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);

}

【讨论】:

  • 谢谢。它在“const subProjData = subProjDoc.data();”行中抛出“subProjDoc.data 不是函数”。我的大脑目前没有产生任何东西,我稍后再看看。这是一个多么简单的逻辑……
  • 它只是缺少一个循环,因为我误解了return Promise.all(getSubProjectsPromises); 的返回类型。您可以使用更新后的代码重试
  • 在旁注中,您可能想查看batched writes,而不是一一写下您的待办事项。
  • 非常感谢您抽出宝贵时间,我得到了它的工作。还有一件事:假设我需要将 userId 写入 unreadyJob,如何?我尝试传递一个包含 subProjects-Query 和 userId 的对象,但在该参数上使用 forEach 时遇到问题。如果你不知道,没问题。我会以某种方式解决的。再次感谢。
  • 哦,对于批量写入:谢谢您的提示,从未考虑过。我想先让它这样工作,然后再重构它。
【解决方案2】:

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

【讨论】:

  • 非常感谢!我更改了代码。但它打印相同的结果。另外:所有的“等待”:代码现在不是同步的吗?如果是这样,那就不应该了!
  • 如果您能给我另一个提示,我将不胜感激。谢谢。
  • 关注@SwiftingDuster 的回答。
【解决方案3】:

我会将逻辑拆分为几个更小的函数(更容易理解、测试和调试 - 顺便说一句,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
   };
}

【讨论】:

  • 感谢您的宝贵时间。 ProjectUrl 是我清理代码时遗留下来的。忽略它。再次感谢!
猜你喜欢
  • 2019-05-16
  • 1970-01-01
  • 2020-12-12
  • 1970-01-01
  • 2021-03-21
  • 2022-01-21
  • 2021-05-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多