NodeJS返回Firestore响应写入Google Sheets失败排查
Google Sheets与Firebase比对后无法写入目标表的代码错误说明
你的代码存在6个核心问题,直接导致写入失败:
- Sheets写入请求未带认证,也未等待异步执行:写入环节你重新初始化了Sheets客户端,没有传入之前完成授权的
jwtClient,请求会直接因无权限被Google服务拒绝;同时spreadsheets.values.update()是异步方法,你没有加await,调用刚触发云函数就可能因主线程执行完毕被回收,请求根本发不出去。 - 全链路异步操作未正确等待,Promise链断裂:
map回调里的Firestore写入set()、两层集合查询get()全是未加await的异步操作,外层的Promise.all(generateDuplicities)只会等待回调的同步代码执行完成,内部的查询、写入逻辑还没跑完,函数就已经结束执行。 - 写入Sheets的数据格式不符合API要求:Sheets的values接口要求传入的
values是二维数组,外层数组对应所有写入行,内层每个子数组对应一行的单元格数据;你当前传入的是一维字符串数组,会直接触发参数格式错误。同时你循环匹配结果时不断往同一个数组追加内容,且固定写入grpr!A12位置,会出现数据互相覆盖的问题。 - Firestore操作无错误捕获:所有Firestore读写操作都没有加异常捕获逻辑,一旦出现权限错误、参数错误,不会打印任何有效日志,根本无法定位问题。
- 重复初始化Sheets客户端:代码开头已经初始化了绑定v4版本的
sheets实例,写入环节不需要再次调用google.sheets({version: 'v4'})新建实例,多余的初始化反而容易漏传配置。 - HTTP触发器未返回响应:整个函数处理逻辑走完后没有调用
response.send()/response.end(),云函数会持续运行直到超时,甚至可能在超时前中断正在执行的写入操作。
修正后的可运行代码示例
import * as functions from 'firebase-functions' import { google } from 'googleapis' import { initializeApp } from 'firebase-admin/app' const serviceAccount = require('../sheets_updater_service_account.json') // 全局只初始化一次sheets客户端 const sheets = google.sheets({version: 'v4'}) import { getFirestore } from "firebase-admin/firestore" initializeApp() const firestore = getFirestore() module.exports.readAndUpdateAdministrativeSheet = functions.https.onRequest(async (request, response) => { try { // =========================== 服务账号认证 =================================== const jwtClient = new google.auth.JWT({ email: serviceAccount.client_email, key: serviceAccount.private_key, scopes: ['https://www.googleapis.com/auth/spreadsheets'] }) await jwtClient.authorize() // ================= 读取源Google Sheet数据 ========================= const { data } = await sheets.spreadsheets.values.get({ auth: jwtClient, spreadsheetId: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', range: `Listagem de pagamento!A2:X6`, }) if (!data.values) { response.status(200).send('无有效数据可处理') return } // 按Sheets要求的二维数组格式存储匹配结果,从A12行开始写入 const outputRows: string[][] = [] const writeStartRow = 12 // ========= 逐行处理源表格数据 ================ const generateDuplicities = data.values.map(async row => { const [idade, nome, cpf, cpf_x, numeroRequerimento, arbitramentoHonorários, valorArbitrado, valorDeferido, valorComplementar, status, resultado, codigoBanco, banco, agencia, conta, dataDoRequerimento, dataRequerimento, dataStatus, comarca, vara, ato, assistidos, email, telefone] = row // 等待Firestore写入完成再走后续逻辑 await firestore.collection("Listagem de pagamento").doc(numeroRequerimento).set({ idade, nome, cpf, cpf_x, numeroRequerimento, arbitramentoHonorários, valorArbitrado, valorDeferido, valorComplementar, status, resultado, codigoBanco, banco, agencia, conta, dataDoRequerimento, dataRequerimento, dataStatus, comarca, vara, ato, assistidos, email, telefone }) // 第一层查询:匹配cpf对应的司法流程文档 const parentQuerySnapshot = await firestore.collection("Processos judiciais").where("documentosDosautores", "==", cpf).get() for (const parentDoc of parentQuerySnapshot.docs) { // 第二层查询:匹配对应仲裁费的子集合文档 const childQuerySnapshot = await parentDoc.ref.collection("fee-arbitrations - Base de Execução").where('arbitramentoDeHonoráriosBE', '==', arbitramentoHonorários).get() for (const childDoc of childQuerySnapshot.docs) { functions.logger.log(`匹配到结果:仲裁费${arbitramentoHonorários},姓名${nome},流程号${childDoc.data().processoBE}`) // 每条匹配结果作为一行,组装为符合要求的子数组 outputRows.push([ `arbitramentoHonorários: ${arbitramentoHonorários}`, `nome: ${nome}`, `processoBE: ${childDoc.data().processoBE}` ]) } } }) // 等待所有行的查询、处理逻辑全部执行完成 await Promise.all(generateDuplicities) // 批量写入目标表格,传入已授权的认证客户端 if (outputRows.length > 0) { await sheets.spreadsheets.values.update({ auth: jwtClient, spreadsheetId: 'YYYYYYYYYYYYYYYYY', range: `grpr!A${writeStartRow}`, valueInputOption: 'USER_ENTERED', resource: { values: outputRows }, }) } response.status(200).send(`处理完成,共写入${outputRows.length}条匹配结果`) } catch (error) { functions.logger.error('函数执行出错', error) response.status(500).send(`执行出错:${error}`) } })
额外注意事项
- 提前把目标表格的编辑权限开放给serviceAccount对应的客户端邮箱,否则就算认证通过也会报权限不足错误。
- 如果匹配结果量大,不要在循环里反复调用Sheets写入接口,统一组装完数据批量写入,能避免触发API频率限制。
内容的提问来源于stack exchange,提问作者Rodrigo Fiad Pasini
相关产品推荐
相关产品推荐

