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

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记录失败状态
        }
    });

关键修改点说明

  1. 异步逻辑统一用async/await:所有异步操作(API请求、Firestore写入)都用await等待完成,避免Promise未处理的问题;
  2. 错误处理:每个异步函数添加try/catch,错误直接抛出,确保能被日志捕获;
  3. 调度器优化:改为async函数,等待数据获取和批量写入完成,用Promise.all并行处理写入提升效率;
  4. Express接口修正:添加express.json()解析请求体,修正setExample路由的参数和数据接收逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 05:33:57