Firebase可调用函数无法返回值问题求助
解决Firebase可调用函数返回null及错误无法传递的问题
你的问题核心在于混用了回调式数据库操作与Promise链,且没有正确返回完整的异步操作链,导致客户端无法获取到最终的GUID,错误也无法传递。下面一步步帮你修正:
关键问题分析
- 你用了
once("value", callback)这种回调写法,它不会返回Promise,导致后续的.then()无法正确衔接异步操作,函数提前返回了空值。 - 错误在回调内部抛出,没有进入Promise链,Firebase无法将这些错误传递给客户端。
- 部分异步操作没有返回Promise,导致Promise链断裂,函数无法等待异步操作完成就返回了。
修正后的完整代码
exports.findCreateGame = functions.https.onCall((data, context) => { console.log("findCurrentGame Called.") const uid = context.auth.uid; let section = ""; let sUsername = ""; let sProfilePic = ""; let currentGames = null; let sGUID = ""; // 第一步:获取用户信息,改用Promise式调用 return admin.database().ref('users/' + uid).once("value") .then(userSnapshot => { const userData = userSnapshot.val(); const wins = parseInt(userData.wins); const losses = parseInt(userData.losses); const lives = parseInt(userData.lives); if (lives < 1) { // 在这里抛出HttpsError,会被客户端catch捕获 throw new functions.https.HttpsError('permission-denied', 'You do not have enough lives to start a new game.'); } sUsername = userData.username; sProfilePic = userData.profilepicture; let ratio = 0; if (losses === 0) { ratio = 100; } else { ratio = (wins / losses) * 100; } // 新手 tier 判断 if (wins + losses < 5) { ratio = 0; } if (ratio <= 33) { section = "noob"; } else if (ratio > 33 && ratio <= 66) { section = "average"; } else { section = "expert"; } // 第二步:获取用户当前参与的游戏,返回Promise继续链 return admin.database().ref('games').orderByChild(uid).once('value'); }) .then(gamesSnapshot => { currentGames = gamesSnapshot.val(); // 生成GUID sGUID = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); // 第三步:处理游戏队列事务,返回Promise const queueref = admin.database().ref('gamequeue/' + section); return queueref.transaction(currentGUID => { if (currentGUID == null) { return sGUID; } else { sGUID = currentGUID; return null; } }); }) .then(transactionResult => { const snapshot = transactionResult.snapshot; if (snapshot.val() != null) { // 创建新游戏 let newAnswer = ""; while (newAnswer.length < 4) { const temp = Math.floor(Math.random() * 9) + 1; const tempStr = temp.toString(); if (!newAnswer.includes(tempStr)) { newAnswer += tempStr; } } const userObj = {username: sUsername, score: 0, profilepicture: sProfilePic}; // 返回创建游戏的Promise,最终返回GUID return admin.database().ref('games/' + sGUID).set({ id: sGUID, requestor: uid, [uid]: userObj, answer: newAnswer, turn: uid, status: 'pending' }).then(() => { return {guid: sGUID}; }); } else { // 加入已有游戏 if (typeof(sGUID) !== 'string') { sGUID = Object.keys(sGUID)[0]; } // 检查是否找到自己的游戏请求 if (currentGames && currentGames[sGUID] != null) { // 将GUID重新放回队列,返回这个操作的Promise return admin.database().ref('gamequeue/' + section).set(sGUID) .then(() => { // 重新执行整个流程?或者返回错误?这里根据你的业务逻辑调整 throw new functions.https.HttpsError('aborted', 'Found own game request, retrying.'); }); } else { // 加入游戏的逻辑,补充完整业务代码 const userObj = {username: sUsername, score: 0, profilepicture: sProfilePic}; return admin.database().ref('games/' + sGUID + '/' + uid).set(userObj) .then(() => { return admin.database().ref('users/' + uid + '/currentGames/' + sGUID).set(true); }) .then(() => { return {guid: sGUID}; }); } } }) .catch(error => { // 捕获所有错误,确保传递给客户端 if (!(error instanceof functions.https.HttpsError)) { // 将普通错误转为HttpsError throw new functions.https.HttpsError('unknown', 'An unexpected error occurred', error); } throw error; // 抛出已定义的HttpsError }); });
核心修正点
- 统一使用Promise式调用:所有数据库操作(
once、transaction、set)都返回Promise,不再用回调参数,确保异步操作能串联成完整的链。 - 每一步都返回Promise:每个
.then()里的异步操作必须返回,这样Promise链才不会断裂,函数会等待所有异步操作完成后再返回结果。 - 正确抛出HttpsError:所有业务错误都要抛出
functions.https.HttpsError,并且要在Promise链中抛出,这样客户端的.catch()才能捕获到。 - 错误统一处理:最后添加
.catch()块,将非HttpsError转为标准的HttpsError,确保所有错误都能传递给客户端。
客户端调用注意事项
你的客户端代码基本没问题,但要确保正确处理返回的result.data.guid:
var newGame = firebase.functions().httpsCallable('findCreateGame'); newGame({}).then(function(result) { var sGameID = result.data.guid; console.log("Game ID:", sGameID); }).catch(function(error) { console.error("Error:", error.message); // 可以根据error.code处理不同类型的错误 });
内容的提问来源于stack exchange,提问作者Tristan
相关产品推荐
相关产品推荐

