【发布时间】:2018-09-30 23:31:24
【问题描述】:
我编写了一些代码,允许用户以类似于 Reddit 的方式对食谱进行投票/否决。
每个单独的投票都存储在名为 votes 的 Firestore 集合中,其结构如下:
{username,recipeId,value}(其中值为 -1 或 1)
配方存储在recipes 集合中,其结构有点像这样:
{title,username,ingredients,instructions,score}
每次用户对菜谱投票时,我都需要在投票集合中记录他们的投票,并更新菜谱上的分数。我想将其作为使用事务的原子操作来执行,因此这两个值不可能不同步。
以下是我到目前为止的代码。我正在使用 Angular 6,但是我找不到任何 Typescript 示例来展示如何在单个事务中处理多个gets(),所以我最终调整了我找到的一些基于 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 文档中所述,“调用事务的函数(事务函数)可能会运行超过如果并发编辑影响事务读取的文档,则执行一次。”
请注意,我已经尝试将 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);
});
}
【问题讨论】:
标签: promise transactions google-cloud-firestore angular-promise