Firebase Cloud Functions调度器无法向Firestore存储数据的问题排查
Firebase Cloud Functions 问题解决方案
背景
我正在用Firebase Cloud Functions构建API系统,实现定期将自有API和第三方API的数据保存到Firestore,所有逻辑集中在/functions/index.js。当前代码如下:
const firebaseAdmin = require("firebase-admin"); const firebaseFunctions = require("firebase-functions"); firebaseAdmin.initializeApp(); const fireStore = firebaseAdmin.firestore(); const express = require("express"); const axios = require("axios"); const cors = require("cors"); const serviceToken = "SERVICE-TOKEN"; const serviceBaseUrl = "https://api.service.com/"; const app = express(); app.use(cors()); const getAllExamples = async () => { var url = `${serviceBaseUrl}/examples?token=${serviceToken}`; var config = { method: "get", url: url, headers: {} }; axios(config).then((res) => { console.log("Data saved!"); return res.data; }).catch((err) => { console.log("Data not saved: ", err); return err; }); } const setExample = async (documentId, dataObject) => { fireStore.collection("examples").doc(documentId).set(dataObject).then(() => { console.log("Document written!"); }).catch((err) => { console.log("Document not written: ", err); }); } app.get("/getAllExamples", (req, res) => { getAllExamples().then((response) => res.send(response)); }); app.put("/setExample", (req, res) => { setExample(req.params.documentId).then((response) => res.send(response)); }); const api = firebaseFunctions.https.onRequest(app); module.exports = { api }; module.exports.updateExamplesRoutinely = firebaseFunctions.pubsub.schedule("0 0 * * *").timeZone("America/Los_Angeles").onRun((context) => { var examples = getAllExamples(); for(var i = 0; i < examples.length; i++) { var example = examples[i]; var exampleId = example["id"]; if(exampleId && example) setExample(exampleId, example); } });
现状:调度器updateExamplesRoutinely能按时触发,但数据未保存到Firestore,日志无相关更新记录。第三方API返回格式如下:
[ { id: "BB345", name: "Tim Gordon", age: 24, email: "tgordon@yahoo.com" }, { id: "CC098", name: "John Doe", age: 28, email: "john.doe@gmail.com" }, { id: "DD777", name: "Adam Smith", age: 39, email: "adamsmith@outlook.com" }, ... ]
问题与解决方案
1. 调度器调用内部API的方式
调度器无需通过HTTP请求调用Express API,直接调用内部定义的getAllExamples函数即可——这是最直接高效的方式,避免额外的HTTP开销。如果是测试场景需要调用HTTP API,可通过axios向函数的部署URL发送请求,但生产环境优先直接调用内部函数。
2. 调度器内保存数据到Firestore
当前代码核心问题是异步逻辑未正确等待:
getAllExamples是async函数,但内部用then/catch未返回Promise,导致调度器中examples是未resolved的Promise而非实际数据,循环无法执行;setExample未等待Firestore写入操作完成,也未返回Promise,调度器无法感知写入状态。
修正方案:用async/await统一处理异步流程,确保数据获取和写入都完成后再结束函数。
3. 第三方API调用的最佳异步方式
最佳实践是使用async/await替代嵌套的then/catch,让代码更易读、易维护,同时能正确处理异步等待和错误捕获。对于批量写入操作,使用Promise.all并行处理可大幅提升效率。
修正后的完整代码
const firebaseAdmin = require("firebase-admin"); const firebaseFunctions = require("firebase-functions"); firebaseAdmin.initializeApp(); const fireStore = firebaseAdmin.firestore(); const express = require("express"); const axios = require("axios"); const cors = require("cors"); const serviceToken = "SERVICE-TOKEN"; const serviceBaseUrl = "https://api.service.com/"; const app = express(); app.use(cors()); app.use(express.json()); // 解析请求体JSON,用于setExample接口 // 修正:用async/await处理第三方API请求,正确返回数据或抛出错误 const getAllExamples = async () => { try { const url = `${serviceBaseUrl}/examples?token=${serviceToken}`; const res = await axios.get(url); // 简化axios调用 console.log("第三方API数据获取成功"); return res.data; } catch (err) { console.error("第三方API请求失败:", err); throw err; // 抛出错误让调用者处理 } } // 修正:await Firestore写入操作,返回Promise const setExample = async (documentId, dataObject) => { try { await fireStore.collection("examples").doc(documentId).set(dataObject); console.log(`文档 ${documentId} 写入成功`); return true; } catch (err) { console.error(`文档 ${documentId} 写入失败:`, err); throw err; } } // 修正Express接口的异步处理 app.get("/getAllExamples", async (req, res) => { try { const data = await getAllExamples(); res.status(200).send(data); } catch (err) { res.status(500).send("获取数据失败"); } }); // 修正:路由添加documentId参数,同时接收请求体数据 app.put("/setExample/:documentId", async (req, res) => { try { const { documentId } = req.params; const data = req.body; await setExample(documentId, data); res.status(200).send("数据保存成功"); } catch (err) { res.status(500).send("保存数据失败"); } }); const api = firebaseFunctions.https.onRequest(app); module.exports = { api }; // 修正调度器:改为async函数,等待数据获取和批量写入完成 module.exports.updateExamplesRoutinely = firebaseFunctions.pubsub.schedule("0 0 * * *") .timeZone("America/Los_Angeles") .onRun(async (context) => { try { const examples = await getAllExamples(); // 等待API返回数据 if (!Array.isArray(examples)) { console.error("API返回数据格式错误,不是数组"); return; } // 用Promise.all并行处理批量写入,提升效率 const writePromises = examples.map(example => { const exampleId = example.id; if (exampleId) { return setExample(exampleId, example); } console.warn("数据缺少id字段,跳过:", example); return Promise.resolve(); }); await Promise.all(writePromises); // 等待所有写入完成 console.log("所有数据批量保存完成"); } catch (err) { console.error("定时任务执行失败:", err); throw err; // 抛出错误让Cloud Functions记录失败状态 } });
关键修改点说明
- 异步逻辑统一用async/await:所有异步操作(API请求、Firestore写入)都用await等待完成,避免Promise未处理的问题;
- 错误处理:每个异步函数添加try/catch,错误直接抛出,确保能被日志捕获;
- 调度器优化:改为async函数,等待数据获取和批量写入完成,用
Promise.all并行处理写入提升效率; - Express接口修正:添加
express.json()解析请求体,修正setExample路由的参数和数据接收逻辑。
内容的提问来源于stack exchange,提问作者cultureshock
相关产品推荐
相关产品推荐

