You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firebase Firestore 单次事务多次调用transaction.get报错解决方案问询

问题原因分析
  • 事务回调的Promise未正确返回:Firestore的runTransaction要求回调函数返回完整的Promise链,用于告知事务所有操作已完成。你代码中第二个t.get的Promise没有被返回到外层,事务在第一个t.get执行完后就被判定为结束,后续调用t.get时事务已经失效,就会抛出过期错误。
  • 异常捕获逻辑无效:runTransaction本身是异步方法,你没有用await或者.catch处理它的异步异常,外层的同步try/catch根本捕获不到事务内部抛出的错误。
  • nextFunction调用时机错误:原代码在事务还未执行完成时就提前调用了nextFunction,逻辑时序错误。
正确实现代码

推荐使用更易读的async/await写法,完全符合Firestore事务的语法要求:

import * as admin from "firebase-admin";

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

  try {
    await admin.firestore().runTransaction(async (t) => {
      // 所有读操作必须放在所有写操作之前,这是Firestore事务的硬性要求
      const coll_A_snapshot = await t.get(collection_A_query);
      if (coll_A_snapshot.empty) {
        console.log("coll_A_snapshot is empty");
        return;
      }

      const coll_B_doc_B1_snapshot = await t.get(collection_B_doc_B1_ref);
      if (!coll_B_doc_B1_snapshot.exists) {
        console.log("coll_B_doc_B1_snapshot does not exist");
        return;
      }

      // 所有写操作放在读操作之后执行
      let counter = coll_B_doc_B1_snapshot.get("COUNTER");
      for (const doc of coll_A_snapshot.docs) {
        counter++;
        t.update(doc.ref, { COUNTER: counter });
      }
      t.update(coll_B_doc_B1_ref, { COUNTER: counter });
    });
  } catch (e) {
    console.log("Transaction failure:", e);
  } finally {
    // 事务执行完成(无论成功失败)后再调用后续方法
    nextFunction();
  }
};

如果你偏好原始Promise链式写法,只要确保所有嵌套的Promise逐层返回即可:

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

  admin.firestore().runTransaction((t) => {
    // 顶层返回整个Promise链
    return t.get(collection_A_query)
      .then((coll_A_snapshot) => {
        if (coll_A_snapshot.empty) {
          console.log("coll_A_snapshot is empty");
          return;
        }
        // 返回内层t.get的Promise
        return t.get(collection_B_doc_B1_ref)
          .then((coll_B_doc_B1_snapshot) => {
            if (!coll_B_doc_B1_snapshot.exists) {
              console.log("coll_B_doc_B1_snapshot does not exist");
              return;
            }
            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_ref, {COUNTER: counter});
          });
      });
  })
  .catch((e) => console.log("Transaction failure:", e))
  .finally(() => nextFunction());
};

你之前设想的提前收集所有DocumentReference的方案完全没必要,Firestore事务本身支持多次读操作,只要遵守「所有读操作在写操作之前执行、Promise链正确返回」两个规则即可正常运行。

内容的提问来源于stack exchange,提问作者sisimans

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 02:24:05