Firestore 事务产生控制台错误:FAILED_PRECONDITION:存储的版本与所需的基本版本不匹配

Der*_*ler 2 transactions promise angular-promise google-cloud-firestore

我已经编写了一些代码,允许用户以类似于 Reddit 的方式对食谱投赞成票/反对票。

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

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

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

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

以下是我到目前为止的代码。我正在使用 Angular 6,但是我找不到任何显示如何在单个事务中处理多个 get() 的 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"
  }
}
Run Code Online (Sandbox Code Playgroud)

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

我应该担心这个错误,还是只是事务重试的正常结果? 如 Firestore 文档中所述,“如果并发编辑影响事务读取的文档,则调用事务的函数(事务函数)可能会运行多次。”

请注意,我已尝试将 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);

  });

}
Run Code Online (Sandbox Code Playgroud)

Der*_*ler 5

根据 Firebase 开发人员 Nicolas Garnier 的说法,“您在这里遇到的是事务在 Firestore 中的工作方式:其中一个事务写入失败,因为数据同时发生了变化,在这种情况下,Firestore 再次重新运行事务,直到它”

换句话说,这是事务重试的正常结果。

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