【问题标题】:Returning cloud Firebase data from multiple promises at the same time同时从多个 Promise 中返回 Cloud Firebase 数据
【发布时间】:2019-03-06 20:07:40
【问题描述】:

我仍在努力更好地理解 Promise。我从 Doug Stevenson 的关于 Promise 的 YouTube 视频中的一些示例开始,然后对其进行了修改以使用我的收藏。此代码与使用区域和城市的示例最相似。

代码如下:

exports.getMatchesNew = functions.https.onCall((data, context) => {
  console.log("In On Call", data);
  return db.collection('matches').get()
  .then(areaSnapshot => {
      const promises = [];
      areaSnapshot.forEach(doc => {
        var area = doc.data();
        // console.log("Area  is ", area.mentees);
        // console.log("area ID is ", area.id);

        // Loop through each mentee for a mentor
        for (const city in area.mentees)
        {
          // console.log("City is ", area.mentees[city]);

          // User Information for current mentee
          const p = db.collection('users').doc(area.mentees[city]).get();

          //User Information for current mentor
          const p2 = db.collection('users').doc(doc.id).get();
          //console.log("Doc ",p);
          // would like to combine this together, which will end up on one row
          // mentor name, mentee name
          promises.push(p, p2);
        }
      })
      
      return Promise.all(promises);
      //response.send(data);
  })
  .then(citySnapshots => {
    const results = [];
    citySnapshots.forEach(citySnap => {
      const data = citySnap.data();
      // this log entry is occuring once for each p and p2 from above.
      // I tried an array reference like citySnap[0] for mentee info and citySnap[1] for mentor info, this did not work.
      console.log("cSnap is: ", data);
      results.push(data);

    })
    return Promise.all(results);
  })
  .catch(error => {
    // Handle the error
    console.log(error);
    //response.status(500).send(error);
  });
});

输出是我得到指导者的名字和姓氏,然后我得到指导者名字和姓氏的输出(在单独的行上)。

在 Firestore 中,matches 集合中的每个文档只是导师的 ID 和学员 ID 的数组。所有用户信息都存储在“用户”集合中。 因此,我尝试遍历每个匹配文档,并为每个导师/学员组合生成一行数据。

当 p 和/或 p2 不可用时,我仍然需要添加一些处理。

我对“p”和“p2”的初衷是:

  1. 返回 p 的名字和姓氏,将它们重命名为 menteeFirstName 和 menteeLastName

  2. 返回 p2 的名字和姓氏,将它们重命名为导师名字和导师姓氏

  3. 组合此信息并返回一组mentorFirstName、mentorLastName、menteeFirstName、menteeLastName。

但是,我用它陷入了困境。我决定将其缩减为工作代码,然后发布。

那么,我可以合并来自“p”和“p2”的数据吗?还是我做错了?

我来自关系数据库背景,因此带有异步调用的 Firestore 集合/文档概念对我来说是一个新概念,我越来越熟悉(但还不够)。

我试图理解那里的各种示例,但我认为我对 Promise 的不熟悉是目前的主要障碍。 我已经尝试过 Roamer 和 Steven Sark 的建议。 Roamer 的建议并没有出错,但我相信它正在放弃承诺。

exports.getMatchesNew = functions.https.onCall((data, context) => {
  return db.collection('matches').get()
  .then(areaSnapshot => {
      const promises = [];
      areaSnapshot.forEach(doc => {
          var area = doc.data();
          for (let city in area.mentees) {
              const p = db.collection('users').doc(area.mentees[city]).get();
              const p2 = db.collection('users').doc(doc.id).get();
              promises.push(
                  Promise.all([p, p2]) // aggregate p and p2
                  .then(([mentee, mentor]) => {
                    var mentorInfo = mentor.data();
                    var menteeInfo = mentee.data();

                      console.log("Mentor: ", mentorInfo.lastName);
                      console.log("mentee: ", menteeInfo.lastName);
                      // return a plain object with all the required data for this iteration's doc
                      return {
                          // 'area': area, // for good measure
                          // 'city': city, // for good measure
                          'mentee': menteeInfo, // ???
                          'mentor': mentorInfo // ???
                      };
                  })
              );
          }
      })
      return Promise.all(promises);
  })
  .catch(error => {
      console.log(error);
      //response.status(500).send(error);
  });
});

我在日志中看到了数据,但没有返回任何记录(或者我在 .vue 页面中错误地引用了它们

<template slot="mentorLastName" slot-scope="row">
          {{row.item.mentor.lastName}}
        </template>
        <template slot="menteelastName" slot-scope="row">
          {{row.item.mentee.lastName}}
        </template>

这适用于结果包含这些相同对象的其他情况。

Steven Sark 的代码也可以根据日志文件运行,没有任何错误。不同之处在于 vue 页面永远不会返回(总是显示为 'thinking')。在过去,这意味着云功能存在错误。云功能日志中没有错误,并且数据未显示在控制台功能日志中(而在 Roamer 版本中)。所以我无法证明这是有效的。

exports.getMatchesNew = functions.https.onCall((data, context) => {
  console.log("In On Call", data);
  return db.collection('matches').get()
  .then(areaSnapshot => {
      const promises = [];
      areaSnapshot.forEach(doc => {
        var area = doc.data();
        // console.log("Area  is ", area.mentees);
        // console.log("area ID is ", area.id);

        // Loop through each mentee for a mentor
        for (const city in area.mentees)
        {
          // console.log("City is ", area.mentees[city]);

          // User Information for current mentee
          const p = db.collection('users').doc(area.mentees[city]).get();

          //User Information for current mentor
          const p2 = db.collection('users').doc(doc.id).get();
          //console.log("Doc ",p);
          // would like to combine this together, which will end up on one row
          // mentor name, mentee name
          promises.push(p, p2);
        }
      })

      return Promise.all(promises);
      //response.send(data);
  })
  .then(citySnapshots => {

    let mentee = citySnapshots[0];
    let mentor = citySnapshots[1];
    console.log("Mentor: ", mentor.lastName);
    return {
      mentee,
      mentor
    };

  })
  .catch(error => {
    // Handle the error
    console.log(error);
    //response.status(500).send(error);
  });
});

我看了这两个修改过的例子,感觉我理解了它,然后当我没有得到结果时我打了自己一巴掌。我觉得这两个都是基于日志条目的承诺,但我不明白如何。在我看来,这些是链接或连接的。没有一个是单独的。

【问题讨论】:

  • Promise.all 需要一个数组或 Promises,而不是数据。见:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • 这是我仍在努力解决的问题。我认为 .get() 是一个承诺,我将这些推到了 promise.all 中。也许我不理解你的评论史蒂文。你是说我在传递数据而我不应该传递数据?
  • @MikeRees,是什么让承诺传递的值/对象变成行?你能用硬编码数据编写一些简单的代码,成功地将行传递给你的模型吗?

标签: javascript promise google-cloud-firestore google-cloud-functions


【解决方案1】:

您的方法可以工作,但由于形成了一个包含ps 和p2s 的promises 数组,因此相当混乱。

据我所知,您希望将每个文档所需的数据捆绑到一个对象中。

有多种方法可以做到这一点。这是一个使用内部 Promise.all() 聚合每个 [p,p2] 对的承诺,然后形成所需的对象。

exports.getMatchesNew = functions.https.onCall((data, context) => {
    return db.collection('matches').get()
    .then(areaSnapshot => {
        const promises = [];
        areaSnapshot.forEach(doc => {
            var area = doc.data();
            for (let city in area.mentees) {
                const p = db.collection('users').doc(area.mentees[city]).get();
                const p2 = db.collection('users').doc(doc.id).get();
                promises.push(
                    Promise.all([p, p2]) // aggregate p and p2
                    .then(([mentee, mentor]) => {
                        // return a plain object with all the required data for this iteration's doc
                        return {
                            'area': area, // for good measure
                            'city': city, // for good measure
                            'mentee': mentee.data(), // ???
                            'mentor': mentor.data() // ???
                        };
                    });
                );
            }
        })
        return Promise.all(promises);
    })
    .catch(error => {
        console.log(error);
        //response.status(500).send(error);
    });
});

所有假设 .get() 是异步的,.data() 是同步的

返回的 Promise 的成功路径将传递一个 {area,mentee,mentor} 对象数组。

可能不是 100% 正确,因为我没有完全理解数据。应该不会太难适应。

【讨论】:

  • 这个建议看起来可行。但是,有一些我还没有解决的语法错误。看起来所有的括号和波浪线都匹配,但显然我错过了一些东西。错误是:“SyntaxError: missing ) after argument list”有什么想法吗?
  • 找不到。尝试(智能地)注释掉行,直到语法错误消失。
  • 解决了语法错误,得到的结果与 Steven 建议的代码更改略有不同。 Steven 什么都没有返回(页面一直在旋转。使用你的页面返回时没有行。也许我在 .vue 代码中没有正确引用它。我像本网站上的其他示例一样引用它结果中的参考对象:
  • 你无疑需要对onCall(data, context)返回的promise所传递的对象数组做一些事情。我对您的代码或 vue 了解不足,无法提供建议。它可能是函数内部的东西,也可能是它的调用者。
【解决方案2】:

由于太大而无法评论,因此移至答案:

我从未遇到过任何以这种方式使用 Promise.all 的示例(使用数据数组),但也许这是来自早期 Promise 库的示例,例如 Bliebird,它不再适用。

这里有一个简单的例子:

Promis.all([ Promise.resolve('one'), Promise.reject(new Error('example failure') ])
.then( ( values ) => {
  //values[0] = data from promise 1; 'one'
  //values[1] = data from promise 2
})
.catch( ( error ) => {
  // note that an error from any promise provided will resolve this catch
})

编辑:

根据要求,您的代码修改为使用 Promise.all:

exports.getMatchesNew = functions.https.onCall((data, context) => {
  console.log("In On Call", data);
  return db.collection('matches').get()
  .then(areaSnapshot => {
      const promises = [];
      areaSnapshot.forEach(doc => {
        var area = doc.data();
        // console.log("Area  is ", area.mentees);
        // console.log("area ID is ", area.id);

        // Loop through each mentee for a mentor
        for (const city in area.mentees)
        {
          // console.log("City is ", area.mentees[city]);

          // User Information for current mentee
          const p = db.collection('users').doc(area.mentees[city]).get();

          //User Information for current mentor
          const p2 = db.collection('users').doc(doc.id).get();
          //console.log("Doc ",p);
          // would like to combine this together, which will end up on one row
          // mentor name, mentee name
          promises.push(p, p2);
        }
      })

      return Promise.all(promises);
      //response.send(data);
  })
  .then(citySnapshots => {

    let mentee = citySnapshots[0];
    let mentor = citySnapshots[1];
    return {
      mentee,
      mentor
    };

  })
  .catch(error => {
    // Handle the error
    console.log(error);
    //response.status(500).send(error);
  });
});

另一个没有你的数据库逻辑的例子,我不太关注:


var response1 = { "foo": "bar" }
var response2 = { "biz": "baz" }


  return Promise.resolve( [ response1, response2 ] )
  .then(areaSnapshot => {
      const promises = [];
      areaSnapshot.forEach(doc => {
          promises.push(Promise.resolve( doc ));
      })

      return Promise.all(promises);
      //response.send(data);
  })
  .then(citySnapshots => {

    let mentee = citySnapshots[0];
    let mentor = citySnapshots[1];
    return {
      mentee,
      mentor
    };

  })
  .catch(error => {
    // Handle the error
    console.log(error);
    //response.status(500).send(error);
  });


【讨论】:

  • 抱歉,我无法克服无法在此评论框中按 ENTER 的事实。我会试试你的建议史蒂文。谢谢。
  • 我不明白如何实施这个建议。我假设 value[0] 将是我的“p”,而 value[1] 将是我的“p2”。这是假设所有被指导者都有信息并且所有指导者都有信息,这样每个数组都将具有相同的元素。我不能做出这样的假设。我正在尝试一次显式检索连接的信息。检索所有学员信息,然后检索所有导师信息,然后尝试连接这些信息对我来说没有意义。也许我只是想错了。您能否就实施此建议提供一些建议?
  • 我已更新我的答案以包含您的代码,已修改。
  • 谢谢史蒂夫。我无法判断这是否正确运行。我在您添加的 .then 中放置了一个 console.log 条目,结果我没有看到任何控制台日志条目。它没有出错。日志有两个典型的函数执行集(开始/结束 204/200。我指的是 并没有显示任何内容。
  • @Mike Reese 抱歉,但我不能完全遵循您的数据库逻辑,所以我用另一个示例编辑了我的回复。我认为你的循环有问题。连接到调试器并单步执行代码,与我刚刚进行的简化编辑相比,找出错误所在
【解决方案3】:

这是最终的工作代码。感谢@Roamer-1888 和“@Steven Stark”

最终的代码有点是他们提供的两种解决方案之间的交叉。非常感谢帮助。

因此,我对 Promise 更放心了。希望我能和他们多合作一点,让自己更舒服,并提高我留住它的机会。

exports.getMatchesNew = functions.https.onCall((data, context) => {
  return db.collection('matches').get()
  .then(areaSnapshot => {
      const promises = [];
      areaSnapshot.forEach(doc => {
          var area = doc.data();
          for (let city in area.mentees) {
              const p = db.collection('users').doc(area.mentees[city]).get();
              const p2 = db.collection('users').doc(doc.id).get();
              if ( typeof p !== 'undefined' && p && typeof p2 !== 'undefined' && p2)
              {
                promises.push(
                    Promise.all([p, p2]) // aggregate p and p2
                );
              }
          }
      })
      return Promise.all(promises);
  })
    .then(citySnapshots => {
    const results = [];
    citySnapshots.forEach(citySnap => {
      var mentor = citySnap[1].data();
      var mentee = citySnap[0].data();
      
      if ( typeof mentee !== 'undefined' && mentee && typeof mentor !== 'undefined' && mentor)
      {
        var matchInfo = {
          mentor: {},
          mentee: {}
        }
        matchInfo.mentor = mentor;
        matchInfo.mentee = mentee;

        results.push(matchInfo);
      }
      else
      {
        console.log("Error missing mentor or mentee record, Need to research: ");
      }
    })
    return Promise.all(results);
  })
  .catch(error => {
      console.log(error);
      //response.status(500).send(error);
  });
});

【讨论】:

  • 第二个 .then() 似乎没有异步执行任何操作。如果是这样,那么它的return Promise.all(results); 可以简化为return results;。我仍然更喜欢我的内部 .then() 来执行所需的数据同步转换。我确定这只是一些简单的调试问题。
猜你喜欢
  • 2020-03-31
  • 2019-01-27
  • 2020-06-20
  • 1970-01-01
  • 2018-04-07
  • 2017-05-04
  • 1970-01-01
  • 2021-05-31
  • 2017-09-14
相关资源
最近更新 更多