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

为何我的Firebase Cloud Function会执行两次?

Firebase Cloud Function生成验证码时被触发两次的问题

我使用Firebase Cloud Function生成一个4位数字格式的验证码,但在Firebase控制台中发现该函数被调用了两次。

触发函数代码

const requestCode = async (uid) => {
  const ref = doc(FIREBASE_FIRESTORE, "codes", uid);
  const data = {
    venue_id: venueID,
    offer_id: offerID,
    status: "pending",
    created_at: serverTimestamp(),
    code: null,
  };
  await setDoc(ref, data, { merge: true });
  listenForCode(uid);
};

生成验证码的函数代码

function generateRandomNumbers() {
  const numbers = [];
  for (let i = 0; i < 4; i++) {
    numbers.push(Math.floor(Math.random() * 10));
  }
  const id = numbers.join("");
  return id;
}

exports.generateCode = functions.firestore
  .document(`codes/{uid}`)
  .onWrite(async (change, context) => {
    const prevData = change.before.data();
    const newData = change.after.data();

    const code = generateRandomNumbers();

    if (prevData.code === null) {
      return await change.after.ref.set({ code: code }, { merge: true });
    } else {
      return null;
    }
  });

控制台演示

控制台演示GIF


问题原因

  1. onWrite触发器特性:onWrite会在文档创建、更新、删除时均触发。你的流程中:
    • 第一步:调用requestCode写入code: null到文档,触发第一次generateCode
    • 第二步:generateCode执行set更新code字段,再次触发onWrite
  2. 条件判断逻辑错误:当文档首次创建时,change.before.data()返回null,此时访问prevData.code会抛出类型错误,触发Cloud Functions的重试机制;即使未报错,第二次触发时prevData.code为null(首次写入的值),条件依然成立,会再次执行写入操作。

修复方案

方案1:改用onCreate触发器

如果仅需要在文档创建时生成验证码,使用onCreate替代onWrite,避免后续更新触发函数:

exports.generateCode = functions.firestore
  .document(`codes/{uid}`)
  .onCreate(async (snap, context) => {
    const data = snap.data();
    if (data.code === null) {
      const code = generateRandomNumbers();
      return await snap.ref.set({ code: code }, { merge: true });
    }
    return null;
  });

方案2:修正onWrite的条件判断

保留onWrite但优化逻辑,确保仅在需要生成验证码时执行写入,同时处理幂等性:

exports.generateCode = functions.firestore
  .document(`codes/{uid}`)
  .onWrite(async (change, context) => {
    const newData = change.after.data();
    // 仅当新数据的code为null,且当前文档未生成过code时执行
    if (newData && newData.code === null) {
      const docSnapshot = await change.after.ref.get();
      const currentCode = docSnapshot.data()?.code;
      if (currentCode === null) {
        const code = generateRandomNumbers();
        return await change.after.ref.set({ code: code }, { merge: true });
      }
    }
    return null;
  });

方案3:优化requestCode的写入逻辑

如果是创建新文档,无需使用{ merge: true },直接写入即可,避免不必要的文档更新触发:

const requestCode = async (uid) => {
  const ref = doc(FIREBASE_FIRESTORE, "codes", uid);
  const docSnapshot = await getDoc(ref);
  const data = {
    venue_id: venueID,
    offer_id: offerID,
    status: "pending",
    created_at: serverTimestamp(),
    code: null,
  };
  // 仅当文档不存在时创建,存在时按需更新
  if (!docSnapshot.exists()) {
    await setDoc(ref, data);
  } else {
    await updateDoc(ref, {
      status: "pending",
      created_at: serverTimestamp(),
      code: null
    });
  }
  listenForCode(uid);
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 08:13:21