Firebase V2定时函数发送推送通知时因Socket断开连接失败求助
Firebase V2定时函数发送推送通知时因Socket断开连接失败求助
我看了你的代码,发现几个很可能导致Socket断开连接、推送发送失败的核心问题,下面给你逐一拆解和修复方案:
1. 最关键的问题:forEach循环里用async/await的陷阱
你在遍历查询结果时用了querySnapshot.forEach(async(doc:any) => { ... }),但forEach并不会等待每个异步操作完成。这会导致你的定时函数可能在所有推送发送、数据库更新都完成前就提前结束,Firebase会强制断开Socket连接,进而出现推送发送失败、操作中断的情况。
修复方案:改用for...of循环(或Promise.all控制并发)
用for...of遍历querySnapshot.docs,这样能确保每个异步操作都被正确等待,直到所有任务完成后函数才会终止。
2. 其他优化点,避免潜在的连接不稳定问题
除了上面的核心问题,还有几个细节可以优化,减少Socket连接压力:
- 批量更新无效Token:如果有大量无效的推送Token,单个
doc.update会多次建立数据库连接,换成WriteBatch批量更新能大幅减少连接开销 - 简化字段检查逻辑,避免冗余的判断
- 控制并发请求数:如果用户量很大,同时发送大量推送可能触发限流或连接不稳定,用
Promise.all结合分块处理会更稳妥
完整修复后的代码示例
const { getFirestore, FieldPath, WriteBatch } = require('firebase-admin/firestore'); const { initializeApp } = require('firebase-admin/app'); const { logger, onSchedule } = require("firebase-functions/v2/scheduler"); const { getMessaging } = require("firebase-admin/messaging"); const { getAuth } = require('firebase-admin/auth'); initializeApp(); const db = getFirestore(); const messaging = getMessaging(); const auth = getAuth(); // 假设你已经实现了这个生成消息的函数 function genMissingDataMessage() { return "请完善你的个人资料数据,解锁完整仪表盘功能!"; } exports.missingDataProgress = onSchedule( "every 24 hours", { timeoutSeconds: 540, retryConfig: { retryCount: 3 } }, // 配置超时和重试策略 async (event) => { logger.log("Start of missingDataProgress"); const dashboardReadyRef = new FieldPath("profile", "dashboardReady"); const appColRef = db.collection("users"); try { const querySnapshot = await appColRef.where(dashboardReadyRef, '==', false).get(); // 用WriteBatch批量更新无效的pushToken,减少数据库连接 const batch = db.batch(); const sendPromises: Promise<void>[] = []; // 改用for...of循环,确保异步操作被正确等待 for (const doc of querySnapshot.docs) { const data = doc.data(); // 简化字段检查,可选链语法更简洁 const pushToken = data?.profile?.pushToken; // 跳过没有有效推送Token的用户 if (!pushToken || pushToken.trim() === '') continue; const pushPayload = { title: "Missing Data!", body: genMissingDataMessage(), image: "", }; logger.info(`准备给用户 ${doc.id} 发送推送,内容:`, pushPayload); const message = { token: pushToken, notification: pushPayload, }; // 把推送请求存入Promise数组,并行处理(如果用户量极大可以分块) const sendPromise = messaging.send(message) .then(() => { logger.info(`推送成功发送给用户 ${doc.id}`); }) .catch((err) => { logger.error(`给用户 ${doc.id} 发送推送失败`, err); // 处理无效Token,加入批量更新队列 if (err.code === 'messaging/invalid-registration-token' || err.code === 'messaging/registration-token-not-registered') { logger.error(`用户 ${doc.id} 的推送Token已失效,将清空`); batch.update(doc.ref, { "profile.pushToken": "" }); } }); sendPromises.push(sendPromise); } // 等待所有推送请求完成 await Promise.all(sendPromises); // 执行批量更新操作 await batch.commit(); logger.log("所有推送和数据库更新操作已完成"); } catch (err) { logger.error("missingDataProgress 函数执行失败", err); throw err; // 抛出错误触发重试(如果配置了重试策略) } } );
额外建议,进一步避免Socket断开问题
- 配置超时和重试:我在代码里加了
timeoutSeconds: 540(最大支持9分钟)和retryConfig,如果函数因为超时或Socket断开失败,Firebase会自动重试 - 分块处理大数量用户:如果你的用户量特别大,把
sendPromises分成每50个一组,每组用Promise.all执行,避免并发过高导致的连接问题 - 监控日志:通过Firebase控制台查看函数的执行日志,确认是否有超时、限流等提示,再针对性优化
内容来源于stack exchange
相关产品推荐
相关产品推荐

