【问题标题】:Nodejs Firebase Transaction - Maximum call stack size exceededNodejs Firebase 事务 - 超出最大调用堆栈大小
【发布时间】:2020-11-16 13:35:36
【问题描述】:

我有一个云功能,它使用事务来更新游戏中的玩家。当 /players 为空时,我试图返回一个地图,但我得到“超出最大调用堆栈大小”。

这是我的云功能:

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 指出的那样,我已将“一次”回调更改为使用 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 node.js firebase firebase-realtime-database google-cloud-functions


    【解决方案1】:

    您的问题很可能来自您返回一个复杂的 JavaScript 对象这一事实,请参阅https://stackoverflow.com/a/52569728/3371862。

    另外,请注意您应该使用once() 方法的promise 版本,因为在Callable Cloud Function 中您必须返回一个promise,该promise 与要发送回的数据对象一起解析客户。

    而不是做

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

    做

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

    有了这个,您将能够正确构建要返回的promise chain。此外,在处理 players 值周围的不同情况时,不是返回将在 .then((result) =&gt; {...}) 块中处理的 JavaScript 对象(这不是必需的,也不是真正合乎逻辑的),而是抛出将在 @ 中处理的错误987654330@块。

    大致如下:

    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);
          }
        });
    });
    

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

    【讨论】:

    • 非常感谢@Renaud Tamec。它终于工作了。我已更改为 promise 版本,更改了错误处理响应,还更改了我在事务中设置数据的方式。我将用我对数据所做的更改来更新我的问题
    猜你喜欢
    • 1970-01-01
    • 2019-07-05
    • 2017-09-02
    • 2014-02-07
    • 2018-06-03
    • 2017-07-02
    • 1970-01-01
    • 2019-02-01
    • 1970-01-01
    相关资源
    最近更新 更多