Firebase云功能-在OnUpdate云触发器中更新其他对象

时间:2020-03-09 12:28:59

标签: firebase promise google-cloud-firestore async-await transactions

假定有一个用户集合,每个用户都与帐户关联,这些帐户保存在一个单独的集合中。每个帐户都有一个余额,该余额会通过某种外部方式(例如下面的http触发器)定期更新。我需要能够查询用户在她所有帐户中的总余额。

我添加了onUpdate触发器,每次帐户更改时都会调用该触发器,并相应地更新总数。但是,似乎有些比赛条件,例如当两个帐户几乎同时更新时:第一个帐户调用onUpdate并更新总余额后,第二个帐户调用onUpdate时,余额仍不更新。我猜测我需要以某种方式使用“交易”进行簿记,但不确定如何使用。

 const data = {
    'users/XXX': {
      email: "a@b.com",
      balance: 0
    },
    "accounts/YYY": {
      title: "Acc1",
      userID: "XXX"
      balance: 0
    },
    "accounts/ZZZ": {
      title: "Acc2",
      userID: "XXX"
      balance: 0
    }
  };

exports.updateAccounts = functions.https.onRequest((request, response) => {
  admin.firestore().collection('accounts').get().then((accounts) => {
    accounts.forEach((account) => {
      return admin.firestore().collection('accounts').doc(account.id).update({balance: 
WHATEVER});    
    })
 response.send("Done");
});

exports.updateAccount = functions.firestore
    .document('accounts/{accountID}')
    .onUpdate((change, context) => {
      const userID = change.after.data().userID;
      admin.firestore().doc("users/"+userID).get().then((user) => {
        const new_balance = change.after.data().balance;
        const old_balance = change.before.data().balance;
        var user_balance = user.data().balance + new_balance - old_balance;
        admin.firestore().doc("users/"+userID).update({balance: user_balance});
      });
    });

2 个答案:

答案 0 :(得分:2)

通过查看您的代码,我们可以看到其中的几部分可能会导致错误的结果。没有彻底测试并重现您的问题,就不可能100%确保纠正它们可以完全解决您的问题,但这很可能是造成问题的原因。

HTTP云功能:

通过forEach()循环,您正在调用几个异步操作(update()方法),但是您不必等待所有这些异步操作都已完成,然后再发送回响应。您应该执行以下操作,使用Promise.all()等待所有异步方法完成,然后再发送响应:

exports.updateAccounts = functions.https.onRequest((request, response) => {

  const promises = [];
  admin.firestore().collection('accounts').get()
  .then(accounts => {
      accounts.forEach((account) => {
        promises.push(admin.firestore().collection('accounts').doc(account.id).update({balance: WHATEVER}));
      return Promise.all(promises);
  })
  .then(() => {
      response.send("Done");
  })
  .catch(error => {....});
});

onUpdate背景触发云功能

您需要正确返回Promises链,以便在云功能完成时向平台指示。以下应该可以解决问题:

exports.updateAccount = functions.firestore
    .document('accounts/{accountID}')
    .onUpdate((change, context) => {

      const userID = change.after.data().userID;

      return admin.firestore().doc("users/"+userID).get()  //Note the return here. (Note that in the HTTP Cloud Function we don't need it! see the link to the video series below)
      .then(user => {
        const new_balance = change.after.data().balance;
        const old_balance = change.before.data().balance;
        var user_balance = user.data().balance + new_balance - old_balance;
        return admin.firestore().doc("users/"+userID).update({balance: user_balance});  //Note the return here.
      });
});

我建议您观看Firebase视频系列中有关“ JavaScript Promises”的3个视频:https://firebase.google.com/docs/functions/video-series/。他们解释了上面已更正的所有关键点。


乍一看,如果您在updateAccounts Cloud Function中修改了几个共享相同account的{​​{1}}个文档,则确实需要实现用户余额更新在事务中,user云功能的多个实例可能会并行触发。交易文档为here

更新: 您可以在updateAccount Cloud Function(未经测试)中实现以下事务:

updateAccounts

答案 1 :(得分:2)

除了@Renaud Tarnec涵盖的their answer之外,您可能还需要考虑以下方法:

批量写入

updateAccounts函数中,您一次要写入许多数据,如果其中任何一个失败,则最终可能得到一个数据库,其中包含正确更新的数据和未能成功更新的数据的混合。进行更新。

要解决此问题,您可以使用批处理写入来原子地写入数据,其中所有新数据都将成功更新,或者不写入任何数据而使数据库处于已知状态。

exports.updateAccounts = functions.https.onRequest((request, response) => {
  const db = admin.firestore();
  db.collection('accounts')
    .get()
    .then((qsAccounts) => { // qs -> QuerySnapshot
      const batch = db.batch();
      qsAccounts.forEach((accountSnap) => {
        batch.update(accountSnap.ref, {balance: WHATEVER});
      })
      return batch.commit();
    })
    .then(() => response.send("Done"))
    .catch((err) => {
      console.log("Error whilst updating balances via HTTP Request:", err);
      response.status(500).send("Error: " + err.message)
    });
});

拆分计数器

与其在文档中存储单个“余额”,不如(根据您要执行的操作)将每个帐户的余额存储在用户的文档中。

"users/someUser": {
  ...,
  "balances": {
    "accountId1": 10,
    "accountId4": -20,
    "accountId23": 5
  }
}

如果需要累积余额,只需将它们添加到客户端即可。如果您需要删除余额,只需在用户文档中删除它的条目即可。

exports.updateAccount = functions.firestore
  .document('accounts/{accountID}')
  .onUpdate((change, context) => {
    const db = admin.firestore();
    const accountID = context.params.accountID;
    const newData = change.after.data();

    const accountBalance = newData.balance;
    const userID = newData.userID;
    return db.doc("users/"+userID)
      .get()
      .then((userSnap) => {
        return db.doc("users/"+userID).update({["balances." + accountID]: accountBalance});
      })
      .then(() => console.log(`Successfully updated account #${accountID} balance for user #${userID}`))
      .catch((err) => {
        console.log(`Error whilst updating account #${accountID} balance for user #${userID}`, err);
        throw err;
      });
  });