【问题标题】:Complete task before running loop for next member在为下一个成员运行循环之前完成任务
【发布时间】:2019-06-19 09:16:47
【问题描述】:

我有一个简单而简短的网站 javascript 代码:

db.collection("classes").doc(data.readGrades).get().then(function(doc) {
          if (doc.exists) {
            const data = doc.data();
            const members = data.members;

            members.forEach(el => {

              db.collection("users").doc(el).collection("grades").get().then(function(querySnapshot) {
                querySnapshot.forEach(function(doc) {
                    const data = doc.data();

                    //some stuff

                });
              });
            })

          } else {
            console.log("No such document!");
          }
        }).catch(function(error) {
          console.log("Error getting document:", error);
        });  

如您所见,我需要运行两个 Firebase Firestore 任务。重要的是首先运行db.collection("classes").doc(data.readGrades).get().then(function(doc) {,并且对于每个成员,此任务遵循db.collection("users").doc(el).collection("grades").get().then(function(querySnapshot) {(注意:第二个任务需要第一个,因为var el)。所以我的问题是我必须等待完成第二个任务,然后才能继续下一个成员的循环。

这正是我的问题:如何在为下一个成员运行循环之前完成第二个任务?

编辑:
我像这样修改了我的 Javascript 代码:

db.collection("classes").doc(data.readGrades).get().then(function(doc) {
    if (doc.exists) {
        const data = doc.data();
        const members = data.members;

        members.reduce((chain, el) => {

          table_number++;
          const html = fillTemplate("grade_table" + table_number, member_name);
          document.getElementById("main_padding").insertAdjacentHTML('beforeend', html);

            return chain.then(() =>
                db.collection("users").doc(el).collection("grades").get().then(function(querySnapshot) {
                    querySnapshot.forEach(function(doc) {
                        const data = doc.data();

                        addToTable("grade_table" + table_number, doc.id, data.mdl, data.klu);
                    });
                })
            )
        }, Promise.resolve());
    } else {
        console.log("No such document!");
    }
}).catch(function(error) {
    console.log("Error getting document:", error);
});

function fillTemplate(table_name, id) {
  console.log("ID OF TABLES  " + table_name);
  return `
  <div class="noten_tabelle_permission" id="noten_tabelle_permission">
    <h1 id="member_name">${id}</h1>
    <table id="${table_name}" style="width:100%">
      <tr>
        <th>Fach</th>
        <th>mündlich</th>
        <th>Klausur</th>
      </tr>
      <!-- Make content with js code -->
    </table>
  </div>
  `;
}

function addToTable(table_name, subject, mdl, klu) {

  var subject_name = getSubjectByNumber(subject);
  var short_subject = getSubjectShortByNumber(subject);

      //Zeile erstellen

      console.log("addToTable " + table_name);

      var y = document.createElement([short_subject]);
      y.setAttribute("id", [short_subject]);
      document.getElementById([table_name]).appendChild(y);

      //Spalten in einer Zeile

      var y = document.createElement("TR");
      y.setAttribute("id", [short_subject]);

      //Spalten in einer Zeile

      var cE = document.createElement("TD");
      var tE = document.createTextNode([subject_name]);
      cE.appendChild(tE);
      y.appendChild(cE);

      var a = document.createElement("TD");
      var b = document.createTextNode([mdl]);
      a.appendChild(b);
      y.appendChild(a);

      var c = document.createElement("TD");
      var d = document.createTextNode([klu]);
      c.appendChild(d);
      y.appendChild(c);


      document.getElementById(table_name).appendChild(y);
}

所以承诺有效。但是现在我有一个问题,即诺言是错误的。如您所见,我想为每个成员运行 Firestore(Database) 任务。所以承诺应该解雇每个新成员。但承诺最终会首先触发。

背景知识:我从数据库中的 ArrayList 中获取成员,对于每个成员,我需要根据该成员获取特定值。当加载成员的值时,我将它们添加到表中,该表为上一个任务中的每个成员(如您所见)填充。正如我所说,承诺将在最后触发。 因此不会将值添加到成员的特定表中。它们将被添加到最后一个表中。

编辑 2: 这是编辑后的代码:

members.reduce((chain, el) => {

              db.collection("users").doc(el).collection("user_data").doc("u").get().then(function (doc) {
                const data = doc.data();
                var member_name = data.name;

                table_number++;
                const html = fillTemplate("grade_table" + table_number, member_name);
                document.getElementById("main_padding").insertAdjacentHTML('beforeend', html);
              })


          return chain.then(((table_num) => {
            return db.collection("users").doc(el).collection("grades").get().then(function (querySnapshot) {
              querySnapshot.forEach(function (doc) {
                const data = doc.data();

                addToTable("grade_table" + table_num, doc.id, data.mdl, data.klu);

              });
            })
          }).bind(null, table_number))
        }, Promise.resolve());

所以这里的问题是我获得 member_name 的任务是异步运行的,而另一部分则不是。所以我只能在加载完所有内容后运行整个任务。因为如您所见,我还需要 member_name。那么如何重新编码它只在加载所有内容(member_name/grades)时运行任务。

【问题讨论】:

标签: javascript firebase foreach promise google-cloud-firestore


【解决方案1】:

您可以使用reduce 代替forEach,并将空Promise 作为初始值传递,以便按顺序处理成员:

db.collection("classes").doc(data.readGrades).get().then(function(doc) {
    if (doc.exists) {
        const data = doc.data();
        const members = data.members;

        members.reduce((chain, el) => {
            return chain.then(() =>
                db.collection("users").doc(el).collection("grades").get().then(function(querySnapshot) {
                    querySnapshot.forEach(function(doc) {
                        const data = doc.data();
                        //some stuff
                    });
                })
            )
        }, Promise.resolve());
    } else {
        console.log("No such document!");
    }
}).catch(function(error) {
    console.log("Error getting document:", error);
});

更新 #1

考虑到更新后的问题,数据可能会添加到最后一个表中,因为在 promise 解析期间,所有 reduce 迭代都已完成(因此 table_name 变量保留上次迭代的值)。您可以通过binding table_name 值来解决它,该值是在 promise 解析时调用的回调:

members.reduce((chain, el) => {

  table_number++;
  const html = fillTemplate("grade_table" + table_number, doc.id);
  document.getElementById("main_padding").insertAdjacentHTML('beforeend', html);

  return chain.then(((table_num) => {
    return db.collection("users").doc(el).collection("grades").get().then(function (querySnapshot) {
      querySnapshot.forEach(function (doc) {
        const data = doc.data();

        addToTable("grade_table" + table_num, doc.id, data.mdl, data.klu);
      });
    })
  }).bind(null, table_number))
}, Promise.resolve());

更新 #2: 为了在单个 reduce 迭代中随后运行多个异步操作,请尝试使用 Promise 的 then 方法链接它们:

members.reduce((chain, el) => {

  return chain
    .then(() => db.collection("users").doc(el).collection("user_data").doc("u").get())
    .then(doc => {
      const data = doc.data();
      var member_name = data.name;

      table_number++;
      const html = fillTemplate("grade_table" + table_number, member_name);
      document.getElementById("main_padding").insertAdjacentHTML('beforeend', html);
      return table_number;
    })
    .then(table_num => {
      return db.collection("users").doc(el).collection("grades").get().then(function (querySnapshot) {
        querySnapshot.forEach(function (doc) {
          const data = doc.data();

          addToTable("grade_table" + table_num, doc.id, data.mdl, data.klu);

        });
      })
    })

}, Promise.resolve());

【讨论】:

  • 请你看看我的编辑,并告诉我为什么它不起作用,好吗?
  • @FilipDegenhart 我已经更新了答案,希望对您有所帮助。
  • @FilipDegenhart 很高兴。
  • 对不起,如果我打扰了你,但请你最后看看我的编辑 2,因为我真的想弄清楚这一切是如何工作的。谢谢:)
  • @FilipDegenhart 抱歉,很遗憾,我不确定如何提取member_name,因为我对您拥有的数据结构了解不多。但是,我假设它可以在el 中找到(传递给reduce 的回调的第二个参数)。如果el是代表成员的对象,很可能包含成员名称等信息。
【解决方案2】:

另一种方法是使用Promise.all(),如下所示:

db.collection("classes").doc(data.readGrades).get()
.then(doc => {
          if (doc.exists) {
            const data = doc.data();
            const members = data.members;

            const promises = []

            members.forEach(el => {
              const usersPromise = db.collection("users").doc(el).collection("grades").get();
              promises.push(usersPromise);
            });

            return Promise.all(promises);

          } else {
            console.log("No such document!");
            throw new Error("No such document!");
          }
})
.then(results => {
    results.forEach(querySnapshot => {
        querySnapshot.forEach(doc => {
                const data = doc.data();
                    //some stuff
        });
    });     
})  
.catch(error => {
    console.log("Error", error);
});

Promise.all(promises) 返回的单个 Promise 解析时返回的 results 是一个包含所有承诺结果的数组,与您在 promises 数组中推送承诺的顺序相同 .

【讨论】:

  • 你能看看我的编辑吗?它在错误的时间触发。
  • 我认为您应该向基于members.reduce()(即 antonku)编写另一个答案的 SO 成员询问。我的回答基于Promise.all()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多