【问题标题】:Cloud Function http request with multiple batch read & writes具有多个批量读取和写入的 Cloud Function http 请求
【发布时间】:2018-11-09 13:28:44
【问题描述】:

我有一个由 http 请求触发的云功能,它打算执行以下操作:

  1. 根据查询获取一定数量的文档。
  2. 对查询的每个文档执行一次读取操作。
  3. 从 (2) 中获取新文档后,执行一些读/写操作(从子集合中删除,将文档添加到另一个子集合,并更新根集合上的文档)。

因此我需要等待(2)和(3)循环然后执行批处理操作的东西。

以下是我目前拥有的代码,当我在本地测试该功能时它可以正常工作。但是我无法将它部署到 Firebase,因为它有诸如“每次都必须返回一个承诺”和“避免嵌套承诺”之类的承诺错误。

exports.finishEvents =  functions.https.onRequest((req, res) => {
  const eventsRef = admin.firestore().collection('events');
  var currentTime = new Date().getTime();
  var currentTimeMinus1h = currentTime - 3600000;

  console.log('----- finishEvents started -----')

  const queryRef = eventsRef.where('finished', '==', false).where('date', '<=', new Date(currentTimeMinus1h)).get().then(function(querySnapshot){
    if (querySnapshot.size > 0) {
        querySnapshot.forEach(function(doc) {

          var owner_id = doc.data().owner_id;
          var event_id = doc.id;
          console.log(owner_id, event_id);

          var userEventOwnerGoingRef = admin.firestore().collection("user_events").doc(owner_id).collection('going').doc(event_id);
          userEventOwnerGoingRef.get().then(doc2 => {
            if (!doc2.exists) {
              console.log('No such document!');
            } else {
              console.log('Document data:', doc2.data());
              var goingIds = doc.data().going_ids;
              console.log('GOING IDS', goingIds);
              var batch = admin.firestore().batch();
              for (var userId in goingIds) {
                if (goingIds.hasOwnProperty(userId)) {
                  console.log(userId + " -> " + goingIds[userId]);
                  var eventRef = admin.firestore().collection("events").doc(event_id);
                  var userEventGoingRef = admin.firestore().collection("user_events").doc(userId).collection('going').doc(doc2.id);
                  var userEventAttendedRef = admin.firestore().collection("user_events").doc(userId).collection('attended').doc(doc2.id);
                  batch.set(userEventAttendedRef, doc2.data());
                  batch.delete(userEventGoingRef)
                  if (userId == doc2.data().owner_id) batch.update(eventRef, {finished: true});
                }
              }
              batch.commit().then(function () {
                return res.status(200).send("Done.");
              });
            }
          })
         .catch(err => {
           console.log('Error getting userEventOwnerGoingRef', err);
           return res.status(200).send("Finished.");
         });
       });
    } else {
        console.log("No events found");
        return res.status(200).send("Finished.");
    }
  })
  .catch(err => {
      console.log('Error getting events', err);
      return res.status(200).send("Finished.");
  });
});

当我在本地对其进行测试时,即使作业已完成,我也会收到一条错误提示

UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Can't set headers after they are sent.

我可以看到我正在发送原始查询的每个文档的结果,我只需要发送一次结果即可完成云功能。

我想我需要返回承诺,然后在步骤 (2) 和 (3) 完成后执行我的所有内容的批处理事务。然而,这是我第一次使用 javascript,我正在为此苦苦挣扎。任何帮助将不胜感激。

【问题讨论】:

  • 你不能忽视来自batch.get().then()的承诺。
  • 正如@Doug 所说,您需要处理每一个承诺。另外,如果可能,我建议尝试将每次读取和写入都作为事务进行。
  • 是的,感谢您的评论。如果可能的话,我真的很感激一个完整的答案。

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


【解决方案1】:

当您的 HTTPS 函数已发送响应但忘记返回在达到超时限制之前解决的承诺时,会遇到 Unhandled promise rejection 错误。这意味着您不会在 HTTPS 函数中返回所有承诺。您的代码应如下所示:

exports.finishEvents =  functions.https.onRequest((req, res) => {
  const eventsRef = admin.firestore().collection('events')
  const currentTime = new Date().getTime()
  const currentTimeMinus1h = currentTime - 3600000

  console.log('----- finishEvents started -----')

  const queryRef = eventsRef
    .where('finished', '==', false)
    .where('date', '<=', new Date(currentTimeMinus1h))

  return queryRef.get().then((querySnapshot) => {
    // Use Promise.all with snapshot.docs.map to combine+return Promise context
    return Promise.all(querySnapshot.docs.map((doc) => {
      const owner_id = doc.get('owner_id')
      const event_id = doc.id
      console.log(owner_id, event_id)

      const userEventOwnerGoingRef = admin.firestore()
        .collection("user_events").doc(owner_id)
        .collection('going').doc(event_id)
      return userEventOwnerGoingRef.get().then((doc2) => {
        if (!doc2.exists) {
          console.log('No such document!')
          return
        } else {
          console.log('Document data:', doc2.data())
          const goingIds = doc.get('going_ids')
          console.log('GOING IDS', goingIds)
          const batch = admin.firestore().batch()
          for (const userId in goingIds) {
            if (goingIds.hasOwnProperty(userId)) {
              console.log(userId + " -> " + goingIds[userId])
              const eventRef = admin.firestore().collection("events").doc(event_id)
              const userEventGoingRef = admin.firestore()
                .collection("user_events").doc(userId).collection('going').doc(doc2.id)
              const userEventAttendedRef = admin.firestore()
                .collection("user_events").doc(userId).collection('attended').doc(doc2.id)
              batch.set(userEventAttendedRef, doc2.data())
              batch.delete(userEventGoingRef)
              if (userId == doc2.get('owner_id')) {
                batch.update(eventRef, {finished: true})
              }
            }
          }
          return batch.commit()
        }
      })
    }))
  })
  .then(() => {
    return res.status(200).send('Done.')
  })
  .catch((err) => {
    console.error(err)
    return res.status(200).send('Finished.')
  })
})

重要的是不要违背你的任何承诺。无论是通过将它们添加到数组并等待它们全部解析/拒绝,还是从它们的作用域/函数中返回它们,始终保持对它们的处理。我希望这会有所帮助。

【讨论】:

  • 您好,抱歉回复缓慢。这很好用,非常感谢。在没有返回其他承诺中,我确实错过了返回 Promise.all(querySnapshot.docs.map((doc) =&gt; {
猜你喜欢
  • 2020-11-19
  • 2021-04-26
  • 2013-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多