Firestore transaction produces console error: FAILED_PRECONDITION: the stored version does not match the required base version

Firestore transaction produces console error: FAILED_PRECONDITION: the stored version does not match the required base version

我写了一些代码,允许用户以类似于 Reddit 的方式对食谱进行投票。

每个单独的投票都存储在名为 votes 的 Firestore 集合中,其结构如下:

{username,recipeId,value}(其中值为 -1 或 1)

食谱存储在 recipes 集合中,其结构有点像这样: {title,username,ingredients,instructions,score}

每次用户对菜谱进行投票时,我都需要将他们的投票记录在投票集合中,并更新菜谱的分数。我想使用事务作为原子操作来执行此操作,因此这两个值永远不会不同步。

以下是我目前的代码。我正在使用 Angular 6,但是我找不到任何说明如何在单个事务中处理多个 gets() 的 Typescript 示例,所以我最终改编了一些我发现的基于 Promise 的 JavaScript 代码.

代码似乎有效,但发生了一些令人担忧的事情。当我快速连续单击 upvote/downvote 按钮时,偶尔会出现一些控制台错误。这些读作 POST https://firestore.googleapis.com/v1beta1/projects/myprojectname/databases/(default)/documents:commit 400 ()。当我查看服务器的实际响应时,我看到了:

{
  "error": {
    "code": 400,
    "message": "the stored version (1534122723779132) does not match the required base version (0)",
    "status": "FAILED_PRECONDITION"
  }
}

请注意,当我慢慢单击按钮时不会出现错误。

我应该担心这个错误,还是它只是事务重试的正常结果? 正如 Firestore 文档中所述,"function calling a transaction (transaction function) might run more than once if a concurrent edit affects a document that the transaction reads."

请注意,我已尝试将 try/catch 块包裹在下面的每个操作周围,并且没有抛出任何错误。为了使代码更易于理解,我在发布之前删除了它们。

非常有兴趣听到任何改进我的代码的建议,无论它们是否与 HTTP 400 错误有关。

async vote(username, recipeId, direction) {

  let value;

  if ( direction == 'up' ) {
    value = 1;
  }

  if ( direction == 'down' ) {
    value = -1;
  }

  // assemble vote object to be recorded in votes collection
  const voteObj: Vote = { username: username, recipeId: recipeId , value: value };

  // get references to both vote and recipe documents
  const voteDocRef = this.afs.doc(`votes/${username}_${recipeId}`).ref;
  const recipeDocRef = this.afs.doc('recipes/' + recipeId).ref;

  await this.afs.firestore.runTransaction( async t => {

    const voteDoc = await t.get(voteDocRef);
    const recipeDoc = await t.get(recipeDocRef);
    const currentRecipeScore = await recipeDoc.get('score');

    if (!voteDoc.exists) {

      // This is a new vote, so add it to the votes collection
      // and apply its value to the recipe's score
      t.set(voteDocRef, voteObj);
      t.update(recipeDocRef, { score: (currentRecipeScore + value) });

    } else {

      const voteData = voteDoc.data();

      if ( voteData.value == value ) {

        // existing vote is the same as the button that was pressed, so delete
        // the vote document and revert the vote from the recipe's score
        t.delete(voteDocRef);
        t.update(recipeDocRef, { score: (currentRecipeScore - value) });

      } else {

        // existing vote is the opposite of the one pressed, so update the
        // vote doc, then apply it to the recipe's score by doubling it.
        // For example, if the current score is 1 and the user reverses their
        // +1 vote by pressing -1, we apply -2 so the score will become -1.
        t.set(voteDocRef, voteObj);
        t.update(recipeDocRef, { score: (currentRecipeScore + (value*2))});
      }

    }

    return Promise.resolve(true);

  });

}

According to Firebase developer Nicolas Garnier、"What you are experiencing here is how Transactions work in Firestore: one of the transactions failed to write because the data has changed in the mean time, in this case Firestore re-runs the transaction again, until it succeeds. In the case of multiple Reviews being written at the same time some of them might need to be ran again after the first transaction because the data has changed. This is expected behavior and these errors should be taken more as warnings."

也就是说,这是事务重试的正常结果。

我使用 RxJS throttleTime 来防止用户通过快速连续单击 upvote/downvote 按钮用事务淹没 Firestore 服务器,这大大减少了这个 400 错误的发生。在我的应用程序中,没有正当理由有人需要每秒剪辑 upvote/downvote 数十次。这不是电子游戏。