在使用一个 transaction.get 后,在交易中阅读额外的文档

Read an additional document in a transaction after using one transaction.get already

我对 Firebase 和 NoSQL 数据库还很陌生,我正在开发服务器端。

我的目标是在一个事务中读取两个集合(并锁定包含的文档以保持一致性)。一方面,我想从集合 A 中读取最多 499 个文档,另一方面,我想从集合 B 中读取一个文档,如以下代码示例所示。

export const transactionTest = function(nextFunction: () => void) {
  const collection_A_reference = admin.firestore().collection("A").limit(499);
  const collection_B_doc_B1_reference = admin.firestore().collection("B").doc("B1");

  try {
    admin.firestore().runTransaction((t) => {
      return t.get(collection_A_reference)
          .then((coll_A_snapshot) => {
            if (!(coll_A_snapshot.empty)) {
              t.get(collection_B_doc_B1_reference)
                  .then((coll_B_doc_B1_snapshot) => {
                    if (coll_B_doc_B1_snapshot.exists) {
                      let counter = coll_B_doc_B1_snapshot.get("COUNTER");
                      for (let i = 0; i < coll_A_snapshot.docs.length; i++) {
                        counter++;
                        t.update(coll_A_snapshot.docs[i].ref, {COUNTER: counter});
                      }
                      t.update(coll_B_doc_B1_snapshot.ref, {COUNTER: counter});
                    } else {
                      console.log("coll_B_doc_B1_snapshot does not exist");
                    }
                  });
            } else {
              console.log("coll_A_snapshot is empty");
            }
          });
    });
  } catch (e) {
    console.log("Transaction failure:", e);
    nextFunction();
  }
  nextFunction();
};

但是,第二个 t.get 似乎是不允许的 并抛出以下错误:

(node:13460) UnhandledPromiseRejectionWarning: Error: 10 ABORTED: The referenced transaction has expired or is no longer valid

有人知道如何实现这个(尤其是语法上)吗?我在谷歌上搜索了很多,但没有找到我想要的东西。也许我在这里推理如何在 firebase 中使用事务时也有错误。一种解决方法可能是在事务之前创建一个 DocumentReference 数组,然后使用它 transaction.getAll() 但这看起来不是很优雅。

如有任何帮助,我将不胜感激:)

此致

问题不在于进行了多次 Transaction#get() 调用,而是您没有正确链接承诺。这会导致事务完成(不执行任何操作)并且您会收到“引用的事务已过期”错误消息。

admin.firestore().runTransaction((t) => {
  return t.get(collection_A_reference)
    .then((coll_A_snapshot) => {
      if (!(coll_A_snapshot.empty)) {
        return t.get(collection_B_doc_B1_reference)  // <-- was missing this return
          .then((coll_B_doc_B1_snapshot) => {
            if (coll_B_doc_B1_snapshot.exists) {
              let counter = coll_B_doc_B1_snapshot.get("COUNTER");
              for (let i = 0; i < coll_A_snapshot.docs.length; i++) {
                counter++;
                t.update(coll_A_snapshot.docs[i].ref, {COUNTER: counter});
              }
              t.update(coll_B_doc_B1_snapshot.ref, {COUNTER: counter});
            } else {
              console.log("coll_B_doc_B1_snapshot does not exist");
            }
          });
      } else {
        console.log("coll_A_snapshot is empty");
      }
    });
});

备注:

  • 交易主体((t) => { /* ... */ } 内的任何内容)可能会被重试多次。所以在做日志记录之类的事情时要小心,因为你最终可能会得到很多包含被忽略数据的日志。相反,您应该 return 有关结果的信息。如果需要重试事务,则丢弃该对象并替换为下一次尝试的结果。当交易成功时,最新的对象被传递回调用者,然后基于该对象进行日志记录。交易中唯一有用的日志是跟踪尝试以及他们花费的时间。
  • 除非您将 await 与 Promise 一起使用,否则用 try/catch 块包围它是没有意义的。
try {
  await admin.firestore().runTransaction((t) => { /* ... */ });
} catch (e) {
  console.log("Transaction failure:", e);
  nextFunction();
}

就重构而言,您可以选择:

const transactionResult = await admin.firestore()
  .runTransaction(async (t) => {
    const coll_B_doc_B1_snapshot = await t.get(collection_B_doc_B1_reference); // check B1 first (only reading 1 document instead of up to 499)
    if (!coll_B_doc_B1_snapshot.exists)
          return { aborted: true, type: "missing-doc" };

    const coll_A_snapshot = await t.get(collection_A_reference);
    if (coll_A_snapshot.empty)
      return { aborted: true, type: "empty" };

    // queue changes
    let counter = coll_B_doc_B1_snapshot.get("COUNTER");
    for (let i = 0; i < coll_A_snapshot.docs.length; i++) {
      counter++; // <- is this meant to increment on every loop?
      t.update(coll_A_snapshot.docs[i].ref, {COUNTER: counter});
    }
    t.update(coll_B_doc_B1_snapshot.ref, {COUNTER: counter});

    return { aborted: false, counter };
  })
  .catch(error => ({ aborted: true, type: "error", error }));

if (transactionResult.aborted) {
  if (transactionResult.type === "empty") {
    console.error(`Update failed, because coll_A is empty`);
  } else if (transactionResult.type === "missing-doc") {
    console.error(`Update failed, because coll_B_doc_B1 is missing`);
  } else {
    console.error("Update failed, because of ${transactionResult.type}", transactionResult.error);
  }
  nextFunction();
  return;
}

console.log(`Counter was successfully updated. B1 now has a COUNTER of ${transactionResult.counter}`);
nextFunction();