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)
根据 Firebase 开发人员 Nicolas Garnier 的说法,“您在这里遇到的是事务在 Firestore 中的工作方式:其中一个事务写入失败,因为数据同时发生了变化,在这种情况下,Firestore 再次重新运行事务,直到它”
换句话说,这是事务重试的正常结果。
我使用 RxJS ThrottleTime 来防止用户通过快速连续点击 upvote/downvote 按钮来使 Firestore 服务器充满事务,这大大减少了这个 400 错误的发生。在我的应用程序中,没有正当理由有人需要每秒剪辑 upvote/downvote 数十次。这不是电子游戏。
| 归档时间: |
|
| 查看次数: |
1125 次 |
| 最近记录: |