【问题标题】:Firestore: How to run a batch write statement in a forEach() loop?Firestore:如何在 forEach() 循环中运行批量写入语句?
【发布时间】:2021-08-31 12:40:32
【问题描述】:

我正在学习 Firestore 的 batch writes 方法,它看起来非常简洁。几乎是异步的!但是,我需要一些帮助来弄清楚在查询上执行 forEach() 时如何运行批处理语句。

我的用例是,当用户删除帖子时,我还需要“清理”并更新/删除与该帖子关联的其他项目。这可能是用户为此帖子创建的所有书签、点赞等。

这是deletePost 函数的示例。 如何对bookmarksQueryusersAnswerQuery 查询运行批处理语句?

async deletePost(post) {
  const response = confirm('Delete this post?')
  const batch = this.$fire.firestore.batch()
  if (response === true && this.userProfile.uid === this.post.uid) {
    try {
      const postRef = this.$fire.firestore
        .collection(`users/${post.uid}/posts`)
        .doc(this.post.id)

      const answerRef = this.$fire.firestore
        .collection('answers')
        .doc(this.post.commentIdWithAnswer)

      const usersAnswerQuery = await this.$fire.firestore
        .collectionGroup('answers')
        .where('id', '==', this.post.commentIdWithAnswer)
        .get()

      const bookmarksQuery = await this.$fire.firestore
        .collectionGroup('bookmarks')
        .where('id', '==', this.post.id)
        .get()

      batch.update(postRef, {
        published: false,
        deleted: true,
        updatedAt: this.$fireModule.firestore.FieldValue.serverTimestamp()
      })

      bookmarksQuery.forEach((doc) => doc.ref.delete()) //<---- how to add this to batch?

      usersAnswerQuery.forEach((doc) => doc.ref.delete()) //<---- how to add this to batch?

      batch.delete(answerRef)

      await batch.commit()
      // To do: delete all user 'likes' associated with this post
      
      
      alert('Post successfully deleted!')
    } catch (error) {
      console.error('error deleting post.', error)
    }
  } else {
    return null
  }
}

【问题讨论】:

    标签: firebase vue.js google-cloud-firestore nuxt.js


    【解决方案1】:

    要将文档删除添加到批处理中,您可以使用 WriteBatch#delete(),就像对 answerRef 所做的那样:

    // prepare the batch
    const batch = firebase.firestore().batch();
    
    // add each doc's deletion to the batch
    docs.forEach((doc) => batch.delete(doc.ref));
    
    // commit the changes
    await batch.commit();
    

    虽然上述方法工作正常,但批量写入具有limit of 500 operations。由于您在整理书签、答案和点赞时可能会达到热门帖子的限制,因此我们需要处理这种情况。我们可以通过跟踪您添加到批次中的操作数量来实现这一点,并在每次达到限制时创建一个新批次。

    // prepare the batch
    let currentBatch = firebase.firestore().batch();
    let currentBatchSize = 0;
    const batches = [ currentBatch ];
    
    // add each doc's deletion to the batch
    docs.forEach((doc) => {
      // when batch is too large, start a new one
      if (++currentBatchSize >= 500) {
        currentBatch = firebase.firestore.batch();
        batches.push(currentBatch);
        currentBatchSize = 1;
      }
    
      // add operation to batch
      currentBatch.delete(doc.ref);
    })
    
    // commit the changes
    await Promise.all(batches.map(batch => batch.commit()));
    

    我在您当前的代码中注意到的其他内容:

    • deletePost 具有不一致的返回类型 Promise&lt;void | null&gt; - 考虑返回 Promise&lt;boolean&gt;(表示成功,因为您正在处理函数中的错误)
    • 您在检查帖子是否可以被当前用户实际删除之前要求用户确认 - 您应该先检查
    • 静默删除其他用户的帖子失败,而不是显示错误(这也应由安全规则强制执行)
    • 静默删除帖子失败,不向用户显示消息
    • 你有一个大的if 块,后面跟着一个小的else 块,你应该翻转它,这样你就可以"fail-fast" 并且不需要缩进大部分代码。

    应用解决方案加上这些其他更改:

    async deletePost(post) {
      if (this.userProfile.uid !== this.post.uid) {
        alert("You can't delete another user's post.");
        return false; // denied
      }
    
      const response = confirm('Delete this post?')
      
      if (!response)
        return false; // cancelled
      
      try {
        const postRef = this.$fire.firestore
          .collection(`users/${post.uid}/posts`)
          .doc(this.post.id)
    
        const answerRef = this.$fire.firestore
          .collection('answers')
          .doc(this.post.commentIdWithAnswer)
    
        const usersAnswerQuery = await this.$fire.firestore
          .collectionGroup('answers')
          .where('id', '==', this.post.commentIdWithAnswer)
          .get()
    
        const bookmarksQuery = await this.$fire.firestore
          .collectionGroup('bookmarks')
          .where('id', '==', this.post.id)
          .get()
          
        let currentBatch = this.$fire.firestore.batch();
        const batches = [currentBatch];    
    
        currentBatch.update(postRef, {
          published: false,
          deleted: true,
          updatedAt: this.$fireModule.firestore.FieldValue.serverTimestamp()
        });
        
        currentBatch.delete(answerRef);
        
        let currentBatchSize = 2;
    
        const addDocDeletionToBatch = (doc) => {
          if (++currentBatchSize >= 500) {
            currentBatch = this.$fire.firestore.batch();
            batches.push(currentBatch);
            currentBatchSize = 1;
          }
        
          currentBatch.delete(doc.ref);
        }
    
        bookmarksQuery.forEach(addDocDeletionToBatch)
        usersAnswerQuery.forEach(addDocDeletionToBatch)
    
        // TODO: delete all user 'likes' associated with this post
    
        // commit changes
        await Promise.all(batches.map(batch => batch.commit()));
        
        alert('Post successfully deleted!')
        return true;
      } catch (error) {
        console.error('error deleting post.', error)
        alert('Failed to delete post!');
        return false;
      }
    }
    

    注意:如果您使用标准 cmets // TODO// FIXME,您可以使用许多识别和突出显示这些 cmets 的工具。

    【讨论】:

    • 哇!太棒了……让我详细研究一下。非常感谢您的贡献...我更像是一名设计师而不是程序员,所以您的回答对我的编码之旅非常有帮助。
    【解决方案2】:

    请按以下步骤操作。不要忘记批量写入(包括删除)的 500 个文档限制。

    async deletePost(post) {
      const response = confirm('Delete this post?')
      const batch = this.$fire.firestore.batch()
      if (response === true && this.userProfile.uid === this.post.uid) {
        try {
          // ...
    
          batch.update(postRef, {
            published: false,
            deleted: true,
            updatedAt: this.$fireModule.firestore.FieldValue.serverTimestamp()
          })
    
          bookmarksQuery.forEach((doc) => batch.delete(doc.ref))
    
          usersAnswerQuery.forEach((doc) => batch.delete(doc.ref))
    
          batch.delete(answerRef)
    
          await batch.commit()
          // To do: delete all user 'likes' associated with this post
          
          
          alert('Post successfully deleted!')
        } catch (error) {
          console.error('error deleting post.', error)
        }
      } else {
        return null
      }
    }
    

    【讨论】:

    • 哇,没错,当我尝试类似的东西时,我忘了添加ref 部分。再次感谢您的帮助,雷诺!现在能与 Firestore 合作真的很兴奋。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-28
    相关资源
    最近更新 更多