Firebase Functions发送响应后仍出现未处理错误的原因排查
问题场景
调用云函数的response.send()发送响应后,模拟器仍提示'Your function was killed because it raised an unhandled error.',日志显示函数超时约60秒,但实际业务操作耗时远低于这个时长。相关代码使用Bluebird的Promise.map处理异步任务:
import * as functions from 'firebase-functions' export const handler = (database: any, urlDate: any) => { const scheduleRef = database.collection('NBASchedule') return functions.https.onRequest(async (req, response) => { const Promise = require('bluebird') const urlList: any = [] scheduleRef.get().then((snapshot:any) => { snapshot.docs.forEach((doc: any) => { const scheduleGame = doc.data() if (scheduleGame.urlString.split('_')[1] === urlDate) { const boxScoreUrl = scheduleGame.boxScoreURL urlList.push('https://' + boxScoreUrl + '/') } }) getLogs() }).catch((err:any) => { console.log(err) }) async function getLogs() { //Promises returned by the bluebird mapper function are awaited for and the returned promise doesn't fulfill until all mapped promises have fulfilled as well. await Promise.map(urlList, (url: any) => { return new Promise( (resolve: any, reject: any) => { setTimeout(function () { resolve(1 + 1) }, 500) }, { concurrency: 1, } ).catch(async (err: any) => { console.log(err) }) }) response.send('Got Current Logs') } }) }
错误原因拆解
1. 异步竞态:Firestore查询未被等待
scheduleRef.get()是异步操作,但代码中没有用await等待它完成就继续执行。getLogs()被调用时,urlList大概率还是空数组,此时Promise.map会直接结束并调用response.send()发送响应。但之后scheduleRef.get()的回调才开始填充urlList,此时云函数已进入终止流程,后续异步操作的异常无法被捕获,最终触发未处理错误提示。
2. getLogs异步函数未被正确等待
在scheduleRef.get().then()回调里调用getLogs(),但没有等待这个异步函数执行完成(getLogs是async函数,调用后返回Promise,但未用await或.then()处理)。如果getLogs内部出现错误,这个Promise的拒绝会变成未处理的异常,触发云函数的报错。
3. Bluebird Promise.map配置参数位置错误
代码把{ concurrency: 1 }作为new Promise的第二个参数,这是错误的。这个并发控制配置应该是Promise.map的第二个参数,当前写法下配置完全无效,可能导致并发行为失控,引发意外问题。
4. 顶层async函数未等待所有异步操作
onRequest的处理函数是async函数,但内部的scheduleRef.get()没有被await,导致顶层函数提前执行完成,云函数开始准备终止,但后台还有未完成的异步任务,这些任务的错误无法被捕获,最终触发超时和未处理错误提示。
修复建议
修复异步等待问题
将scheduleRef.get()的回调写法改为await,并等待getLogs完成:
// 替换原有的scheduleRef.get().then(...)代码块 try { const snapshot = await scheduleRef.get(); snapshot.docs.forEach((doc: any) => { const scheduleGame = doc.data(); if (scheduleGame.urlString.split('_')[1] === urlDate) { const boxScoreUrl = scheduleGame.boxScoreURL; urlList.push('https://' + boxScoreUrl + '/'); } }); await getLogs(); // 等待getLogs异步函数执行完成 } catch (err) { console.log(err); response.status(500).send('获取赛程数据失败'); }
修正Promise.map的参数位置
把并发配置移到Promise.map的第二个参数位置:
await Promise.map(urlList, (url: any) => { return new Promise((resolve: any, reject: any) => { setTimeout(function () { resolve(1 + 1); }, 500); }).catch(async (err: any) => { console.log(err); }); }, { concurrency: 1 }); // 正确的配置位置
确保所有异步操作被顶层函数等待
保证onRequest内部的所有异步任务都被await,避免云函数提前进入终止流程,导致未处理异常。
内容的提问来源于stack exchange,提问作者Louis Sankey

