Nodejs Firebase事务-超过了最大调用堆栈大小



我有一个云函数,它使用事务来更新游戏中的玩家。当/players为null时,我试图返回一个Map,但我得到了"超过了"最大调用堆栈大小";。

这是我的云功能:

export const addUserToGame = functions.https.onCall((data, context) => {
// Expected inputs - game_id(from data) and UID(from context)
if (context.auth == null) {
return {
"status": 403,
"message": "You are not authorized to access this feature"
};
}
const uid = context.auth.uid;
const game_id = data.game_id;
let gameIDRef = gamesRef.child(game_id);
return gameIDRef.once("value", function (snapshot) {
let players: Map<String, Number> = snapshot.child("players").val();
let max_players: Number = snapshot.child("max_players").val();
if (players != null && players.has(uid)) {
return {
"status": 403,
"message": "Player already in the game"
}
} else if (players != null && players.size >= max_players) {
return {
"status": 403,
"message": "Game is already full"
}
} else {
let playersNodeRef = gamesRef.child(game_id).child("players");
return playersNodeRef.transaction(t => {
if (t === null) {
return new Map<String, Number>().set(uid, 1);//trying to set a map with the player data, when the /players is null
} else {
let playersData: Map<String, Number> = t;
if (playersData.size >= max_players) { // rechecking
return;
} else {
playersData.set(uid, 1);
return playersData;
}
}
}).then(result => {
if (result.committed) { // if true there is a commit and the transaction went through
return {
"status": 200,
"message": "User added to game successfully"
}
} else {
return {
"status": 403,
"message": "Unable to add user at this time. Please try again"
}
}
}).catch(error => {
return {
"status": 403,
"message": error
}
});
}
});
});

这是堆栈跟踪:

addUserToGame
Function execution took 1423 ms, finished with status code: 500
at /workspace/node_modules/lodash/lodash.js:13401:38
at encode (/workspace/node_modules/firebase-functions/lib/providers/https.js:179:18)
at Function.mapValues (/workspace/node_modules/lodash/lodash.js:13400:7)
at baseForOwn (/workspace/node_modules/lodash/lodash.js:2990:24)
at /workspace/node_modules/lodash/lodash.js:4900:21
at keys (/workspace/node_modules/lodash/lodash.js:13307:14)
at isArrayLike (/workspace/node_modules/lodash/lodash.js:11333:58)
at isFunction (/workspace/node_modules/lodash/lodash.js:11653:17)
at baseGetTag (/workspace/node_modules/lodash/lodash.js:3067:51) 
at Object (<anonymous>)
Unhandled error RangeError: Maximum call stack size exceeded

如何将映射设置为/players节点?


代码有不止一个问题,正如@Renaud所指出的,我已经将"once"回调更改为使用promise版本。此外,我在发送回交易中的数据时遇到了问题。我发送的数据使用复杂的JS对象,如Map((,但经过一番努力(语法(,我将其更改为普通的JS对象(类似json的结构(。请看下面我的更改:

if (t === null) {
return [{ [uid]: { "status": 1 } }]; // if null, create an array and add an object to it
} else {
let playersData = t;
if (playersData.size >= max_players) { // rechecking
return;
} else { // if not null create an object and add to the existing array
playersData.push({ 
[uid]: {
"status": 1
}
});
return playersData;
}
}

您的问题很可能是因为返回了一个复杂的JavaScript对象,请参阅https://stackoverflow.com/a/52569728/3371862.

此外,请注意,您应该使用once()方法的promise版本,因为在可调用云函数中,您必须返回一个与要发送回客户端的数据对象解析的promise。

而不是做

return gameIDRef.once("value", function (snapshot) {...});

进行

return gameIDRef.once("value").then(snapshot => {...});

有了这个,您将能够正确地构建要返回的承诺链。此外,在处理players值周围的不同情况时,不是返回将在.then((result) => {...})块中处理的JavaScript对象(这不是必需的,也不是真正的逻辑(,而是抛出将在catch()块中处理过的错误。

大致如下:

export const addUserToGame = functions.https.onCall((data, context) => {
// Expected inputs - game_id(from data) and UID(from context)
if (context.auth == null) {
return {
status: 403,
message: 'You are not authorized to access this feature',
};
// IMHO better to do  throw new functions.https.HttpsError('...', ...);
}
const uid = context.auth.uid;
const game_id = data.game_id;
let gameIDRef = gamesRef.child(game_id);
return gameIDRef
.once('value')
.then((snapshot) => {
let players: Map<String, Number> = snapshot.child('players').val();
let max_players: Number = snapshot.child('max_players').val();
if (players != null && players.has(uid)) {
throw new Error('Player already in the game');
} else if (players != null && players.size >= max_players) {
throw new Error('Game is already full');
} else {
let playersNodeRef = gamesRef.child(game_id).child('players');
return playersNodeRef.transaction((t) => {
if (t === null) {
return new Map<String, Number>().set(uid, 1); //trying to set a map with the player data, when the /players is null
} else {
let playersData: Map<String, Number> = t;
if (playersData.size >= max_players) {
// rechecking
return;
} else {
playersData.set(uid, 1);
return playersData;
}
}
});
}
})
.then((result) => {
if (result.committed) {
// if true there is a commit and the transaction went through
return {
status: 200,
message: 'User added to game successfully',
};
} else {
// probably throw an error here
return {
status: 403,
message: 'Unable to add user at this time. Please try again',
};
}
})
.catch((error) => {
if (error.message === 'Player already in the game') {
throw new functions.https.HttpsError('...', error.message);
} else if (error.message === 'Game is already full') {
throw new functions.https.HttpsError('...', error.message);
} else {
throw new functions.https.HttpsError('internal', error.message);
}
});
});

有关如何处理可调用云函数中的错误的更多详细信息,请参阅此处。

最新更新