【问题标题】:Inner Firestore promise inside forEach, is executed after the outer promiseforEach 内部的 Firestore 承诺,在外部承诺之后执行
【发布时间】:2018-12-26 13:37:27
【问题描述】:

我正在使用 node.js 从 Firestore 中的 2 个不同集合中获取数据。

这个问题和这个类似,但是这个问题没有答案:Nested Firebase Firestore forEach promise queries

就我而言,我正在创建一个类似 instagram 的应用程序,其中有一个“时间线”集合。在时间线文档中,我有一个用户密钥。通过该用户密钥,我想从“用户”集合中执行另一个查询。

所以逻辑上这里是查询步骤:

  1. 获取包含用户密钥的时间线数据(以数组形式)。
  2. 使用该用户密钥,获取用户数据(单个数据)
  3. 向客户端返回 JSON 响应。

问题是,JSON 响应是在获取用户数据之前返回的。

这是我的代码:

tlRoute.get((req,res)=>{

  //reading from firestore
  let afs = req.app.get('afs');

  var timelineArr = [];

  let timelineRef = afs.collection(`timeline/${req.params.key}/timeline`);
  timelineRef.get().then((snapshot) => {

    snapshot.forEach((doc) => {

      if(!doc.exists){
        console.log('No such document!');
      } else {

        //populating timelineData with doc.data()
        console.log('populating timelineData');
        let timelineData = doc.data();

        let userRef = afs.doc(`user/${doc.data().userKey}`);
        userRef.get().then((doc) => {

          //adding user details to timelineData
          console.log('adding user details to timelineData');
          timelineData.profileImageUrl = doc.data().profileImageUrl;
          timelineData.username = doc.data().username;
          timelineArr.push(timelineData);

        });
      }

    });
  })
  .catch(err => {
    console.log(err);
  });

  console.log('this is the final timelineArr', timelineArr);

  //returning response json data to client
  return res.json(timelineArr);

});

在控制台日志中,这是我得到的日志:

this is the final timelineArr []
populating timelineData
populating timelineData
adding user details to timelineData
adding user details to timelineData

任何帮助将不胜感激。

【问题讨论】:

  • 内部userRef.get()调用是异步的;这就是使用.then() 回调处理结果的原因。
  • 那我怎么能等到userRef.get()先完成后,再调用res.json(timelineArr)呢?

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


【解决方案1】:

我尝试重构您的代码以产生相同的输出,添加了一些方法,每个方法都有一个与特定类型对象相关的更简单、独立、可测试的目的。

// return a promise that resolves to a user doc snapshot with the given key
function getUserWithKey(db, userKey) {
  return db.doc(`user/${userKey}`).get();
}

// return a promise that resolves to a timeline object augmented to include
// its doc id and its associated user's username
function timelineObject(db, timelineDocSnapshot) {
  let timelineObject = timelineDocSnapshot.data();
  timelineObject.postKey = timelineDocSnapshot.id;
  return getUserWithKey(db, timelineObject.userKey).then(userDocSnapshot => {
    timelineObject.username = userDocSnapshot.data().username;
    timelineObject.profileImageUrl = userDocSnapshot.data().profileImageUrl;
    return timelineObject;
  });
}

// return a promise that resolves to all of the timeline objects for a given key
function getTimeline(db, key) {
  let timelineRef = db.collection(`timeline/${key}/timeline`);
  return timelineRef.get().then(querySnapshot => {
    let promises = querySnapshot.docs.map(doc => timelineObject(db, doc));
    return Promise.all(promises);
  });
}

// get route for a timeline
tlRoute.get((req,res)=>{
  let db = req.app.get('db');
  let key = req.params.key;
  return getTimeline(db, key).then(timelineObjects => {
    return res.json(timelineObjects);
  });
})

使用 async / await 语法可以进一步改进此代码。

【讨论】:

  • 非常感谢丹!它运行良好。我还没有使用异步等待,但是这个工作正常,所以我会继续使用它。我在那里稍作修改,请检查并接受。
【解决方案2】:

Sooo Firebase 使用回调或承诺(“.then((snapshot) => {})” 事物)在从 Firestore 检索数据后运行。您正在做的是在回调方法运行之前返回timelineArr,因此在它被Firestore中的数据填充之前!

对此的一种解决方案是将返回语句移动到回调方法中并使整个函数异步。它会是这样的:

var timelineArr = [];
async function RetrieveData() {
    let timelineRef = afs.collection(`timeline/${req.params.key}/timeline`);
    await timelineRef.get().then((snapshot) => {

    snapshot.forEach((doc) => {

      if(!doc.exists){
        console.log('No such document!');
      } else {

        //populating timelineData with doc.data()
        console.log('populating timelineData');
        let timelineData = doc.data();

        let userRef = afs.doc(`user/${doc.data().userKey}`);
        userRef.get().then((doc) => {

          //adding user details to timelineData
          console.log('adding user details to timelineData');
          timelineData.profileImageUrl = doc.data().profileImageUrl;
          timelineData.username = doc.data().username;
          timelineArr.push(timelineData);

        });
      }

    });

      //returning response json data to client
      return res.json(timelineArr);

    }).catch(err => {
      console.log(err);
    });
}

RetrieveData()

console.log('this is the final timelineArr', timelineArr);

祝你好运!

最好的问候,埃斯基尔斯。

【讨论】:

  • 我在 node.js 中做这个。您是否在 tlRoute.get((req,res)=>{}) 中创建异步功能?我在这里遇到了一个错误。无法在该 get 函数中创建函数。
  • 是的。啊。好的。然后尝试创建一个函数,而是看看你是否可以使tlRoute.get((req,res)=>{异步。就像这样async tlRoute.get((req,res)=>{。如果这不起作用,请尝试在get 之外创建函数,并在tlRoute.get((req,res)=>{ 内部运行函数,并将reqres 作为参数传递。像这样async function(req, res) {…}。祝你好运!
【解决方案3】:

我决定使用for 循环遍历内部承诺。

这是我的代码:

tlRoute.get((req,res)=>{

  let db = req.app.get('db');
  let key = req.params.key;

  var timelineArr = [];

  getData(db, key, timelineArr).then(results => {

    for (let index = 0; index<results.length; index++) {
      timelineArr[index].profileImageUrl = results[index].data().profileImageUrl;
      timelineArr[index].username = results[index].data().username;
    }
    console.log(results.length);

    console.log(timelineArr);

    console.log('this is the final timelineArr');
    //returning response json data to client 
    return res.json(timelineArr);

  });

});

function getData(db, key, timelineArr){
  let timelineRef = db.collection(`timeline/${key}/timeline`);
  return timelineRef.get().then((snapshot) => {

    console.log('snapshot length: ', snapshot.length);

    var promiseArr = [];

    snapshot.forEach((doc) => {

      if(!doc.exists){
        console.log('No such document!');
      } else {

        //populating timelineData with doc.data()
        console.log('populating timelineData');
        let timelineData = doc.data();
        timelineData.postKey = doc.id;
        timelineArr.push(timelineData);

        console.log('userKey: ', doc.data().userKey);
        let userRef = db.doc(`user/${doc.data().userKey}`);
        promiseArr.push(userRef.get());

      }
    });
    return Promise.all(promiseArr);

  })
  .catch(err => {
    console.log(err);
  });
}

【讨论】:

  • 这个代码可以通过更好的组织来改进。对timelineArr 参数产生副作用并返回承诺的选择是不寻常的。让这两种方法负责部分数据组织也很尴尬,IMO。它可能会起作用,但是当您以后想要修复或改进它时,它也可能会让您头疼。
  • 那么我怎样才能通过更好的组织来改进代码呢?
  • 我添加了一个更全面地描述我的建议的答案
猜你喜欢
  • 2018-04-17
  • 1970-01-01
  • 2015-06-24
  • 2021-08-11
  • 1970-01-01
  • 2016-08-20
  • 1970-01-01
  • 2016-01-10
相关资源
最近更新 更多