如何在Firebase云函数中将Firestore事务代码封装到其他函数并返回对应Promise
问题解答
核心疑问回复
你必须返回事务返回的Promise,原因如下:
- Firebase 云函数的异步触发器(比如 Firestore 的
onCreate)依赖返回的Promise判断异步任务是否执行完成,如果不返回,函数实例会在事务执行完毕前就被系统回收,大概率出现事务执行中断、数据不一致的问题。 - 只有返回事务的
Promise,才能正确捕获事务执行过程中抛出的异步错误(比如写入冲突、权限不足、调用失败等),方便做错误处理和告警。
现有代码存在的问题
- 异步错误捕获失效:你当前的同步
try/catch只能捕获代码同步执行阶段的错误,db.runTransaction执行过程中抛出的异步错误无法被捕获,会直接导致函数执行异常无日志。 - 事务内部嵌套第三方API调用不符合规范:Firestore事务遇到写入冲突时会自动重试最多5次,你将
createStripeUser放在事务内部,一旦事务重试就会重复调用Stripe接口,哪怕使用了幂等键也会产生不必要的开销,极端情况下会出现数据不一致。 - 冗余的
Promise包装:return Promise.all([transactionPromise])没有实际意义,直接返回事务本身的Promise即可。 - 存在未定义变量:代码中用到的
firebaseUserEmail、currentUserNr、idempotencyKey在doOnCreatedUserDocs作用域内没有定义,执行时会抛出引用错误。
优化后代码示例
export const dbUserDocsOnCreated = functions .region(constants.region) .firestore .document("users/{id}") .onCreate((snapshot, context) => doOnCreatedUserDocs(db, stripe, snapshot, context)); export async function doOnCreatedUserDocs( db: FirebaseFirestore.Firestore, stripe: Stripe, snapshot: functions.firestore.QueryDocumentSnapshot, context: functions.EventContext ) { // 补充缺失的变量定义示例,可根据实际业务调整 const snapshotData = snapshot.data() const firebaseUserEmail = snapshotData.email ?? '' const idempotencyKey = context.eventId // 直接使用云函数eventId作为幂等键可靠性最高 try { // 第一步:执行事务获取自增用户ID,同时做幂等校验 const createdUserNr: string | null = await db.runTransaction(async (t) => { const freshUserDoc = await t.get(snapshot.ref) const freshData = freshUserDoc.data() // 二次校验避免重复执行 if (!freshData || !isDataNullOrEmpty(freshData.eventID)) { return null } // 原子操作获取ID并更新自增计数器 const userId = await getIdTransactionally(t, db, constants.collectionUsers) await incrementIdTransactionally(t, db, constants.collectionUsers) return userId }) // 已经执行过直接返回 if (!createdUserNr) return { success: true, repeat: true } // 第二步:事务外调用Stripe接口创建用户 const createdStripeUser = await createStripeUser(stripe, snapshotData, firebaseUserEmail, createdUserNr, snapshot.id, idempotencyKey) // 第三步:更新用户文档信息 await db.runTransaction(async (t) => { await updateUserDocsTransactionally(t, snapshot.ref, createdUserNr, idempotencyKey) }) return { success: true } } catch (err) { // 这里添加错误日志上报逻辑 console.error('用户创建流程失败', err) throw err // 抛出错误可触发云函数自动重试(需提前配置重试策略) } } // 原有工具函数逻辑无需调整,如下保留 export async function incrementIdTransactionally( transaction: FirebaseFirestore.Transaction, db: FirebaseFirestore.Firestore, doc: string ): Promise<FirebaseFirestore.Transaction> { const incrementor: FirebaseFirestore.FieldValue = FirebaseFirestore.FieldValue.increment(1); const collectionReference: FirebaseFirestore.DocumentReference<FirebaseFirestore.DocumentData> = db.collection(constants.collectionAutoIds).doc(doc) return transaction.update(collectionReference, { id: incrementor }) } async function updateUserDocsTransactionally( transaction: FirebaseFirestore.Transaction, ref: FirebaseFirestore.DocumentReference, generatedUserNr: String, eventId: string, ): Promise<FirebaseFirestore.Transaction> { return transaction.set(ref, { userNr: generatedUserNr, eventId: eventId, optionalAddress: FirebaseFirestore.FieldValue.delete() }, { merge: true }) } function createStripeUser( stripe: Stripe, data: FirebaseFirestore.DocumentData, eMail: string, usernr: string, uid: string, idempotencKey: string, ): Promise<Stripe.Response<Stripe.Customer>> { return stripe.customers.create({ name: data.fullName, email: eMail, description: "Created by Firebase Cloud-Functions", metadata: { uuid: uid, userNr: usernr }, preferred_locales: ["de-DE"], }, { idempotencyKey: idempotencKey }) }
内容的提问来源于stack exchange,提问作者Andrew
相关产品推荐
相关产品推荐

