Firebase pubsub定时函数模拟器正常 生产环境未执行Stripe调用提前退出
问题根因
- 核心问题出在异步遍历的写法上:
Array.prototype.forEach不支持等待异步回调执行完成,你在forEach里传入的async函数不会被主函数等待,主函数触发完所有遍历任务后就直接返回了。生产环境的Cloud Functions会在函数返回后立即冻结执行上下文,未执行完的Stripe调用、Firestore更新操作都会被直接中断,因此你看不到后续的执行日志。 - 本地emulator运行正常是因为本地调试环境不会在函数返回后立即冻结进程,后台未完成的异步任务仍能继续执行,所以能跑完完整流程,这是本地和生产环境运行机制的差异导致的。
修复方案
将forEach遍历替换为支持异步等待的写法,让主函数等待所有异步操作执行完成后再返回,可选两种实现方式:
1. 串行执行(适合用户量小、避免触发Stripe接口限流)
export const reportUsage = fun.pubsub.schedule("0 0 1 * *").onRun(async (context) => { functions.logger.debug("Initialising db"); const db = admin.firestore(); const users = await db.collection("users").get(); // 替换forEach为for...of 逐个等待异步操作完成 for (const doc of users.docs) { functions.logger.debug("Got list of users. Looping.."); const userData = doc.data(); const SOME_DATA:number = userData["SOME_DATA"]; functions.logger.debug("got SOME_DATA of this user"); const SOME_DATAIntPart:number = parseInt(SOME_DATA.toFixed(20).split(".")[0]); const SOME_DATAFloatPart:number = parseFloat("0." + SOME_DATA.toFixed(20).split(".")[1]); const subItemId = userData["stripeInfo"]["subscription"]["items"]["data"][0]["id"]; functions.logger.debug("got sub id of this user"); await stripe.subscriptionItems.createUsageRecord(subItemId, { quantity: SOME_DATAIntPart, timestamp: admin.firestore.Timestamp.now().seconds, }, { timeout: 60, maxNetworkRetries: 5, }); functions.logger.debug("got reported to stripe"); await doc.ref.update({ "SOME_DATA": SOME_DATAFloatPart, }); functions.logger.debug("updated SOME_DATA"); } return null; });
2. 并行执行(适合用户量大、提升处理效率)
export const reportUsage = fun.pubsub.schedule("0 0 1 * *").onRun(async (context) => { functions.logger.debug("Initialising db"); const db = admin.firestore(); const users = await db.collection("users").get(); // 用Promise.all批量等待所有异步任务完成 await Promise.all(users.docs.map(async (doc) => { functions.logger.debug("Got list of users. Looping.."); const userData = doc.data(); const SOME_DATA:number = userData["SOME_DATA"]; functions.logger.debug("got SOME_DATA of this user"); const SOME_DATAIntPart:number = parseInt(SOME_DATA.toFixed(20).split(".")[0]); const SOME_DATAFloatPart:number = parseFloat("0." + SOME_DATA.toFixed(20).split(".")[1]); const subItemId = userData["stripeInfo"]["subscription"]["items"]["data"][0]["id"]; functions.logger.debug("got sub id of this user"); await stripe.subscriptionItems.createUsageRecord(subItemId, { quantity: SOME_DATAIntPart, timestamp: admin.firestore.Timestamp.now().seconds, }, { timeout: 60, maxNetworkRetries: 5, }); functions.logger.debug("got reported to stripe"); await doc.ref.update({ "SOME_DATA": SOME_DATAFloatPart, }); functions.logger.debug("updated SOME_DATA"); return null; })); return null; });
内容的提问来源于stack exchange,提问作者Bashar Shehab
相关产品推荐
相关产品推荐

