javascript Nodejs Firebase Transaction -更新节点并在条件失败时发送错误

7rfyedvj  于 2023-09-29  发布在  Java
关注(0)|答案(3)|浏览(73)

该场景是当玩家在应用程序中点击“加入游戏”时,调用云函数将玩家添加到firebase数据库,并将结果作为响应传递。
数据库结构:

/games
|--/game_id
|----/players => this is a json array of uids and their status([{"uid1": true}, {"uid2":true},...])
|----/max_players = 10

云函数应该检查/players的大小是否小于/max_players,只有这样,它才应该用播放器的uid更新/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.transaction(function (t) {

        if (t === null) {
            // don't do anything, can i just do return;
        } else {

            let playersNode;
            let isAlreadyPlayer = false;
            if (t.players) { 
                console.log("players is not empty");

                playersNode = t.players;
                let players_count = playersNode.length;
                let max_players: Number = t.max_players;

                if (players_count >= max_players) {
                    // how can i return an error from here?
                }

                for (var i = 0; i < playersNode.length; i++) {
                    if (playersNode[i].uid === uid) {
                        isAlreadyPlayer = true;
                    }
                    break;
                }

                if (!isAlreadyPlayer) {
                    playersNode.push({
                        [uid]: "active"
                    });
                }
              
             t.players = playersNode;
             return t;

            } else {
                playersNode = [];
                playersNode.push({
                    [uid]: "active"
                });

                t.players = playersNode;
                return t;
            }

        }

    },function(error, committed, snapshot) {

        if(committed) {
            return {
                "status": 200,
                "message": "Player added successfully"
            };
        } else {
            return {
                "status": 403,
                "message": "Error adding player to the game"
            };
        }

    });

});

请看上面代码中的注解,当一个条件失败时,我如何发送回响应?
感谢@Doug和@Renaud的快速回复,我还有几个问题:
Q1.所以我必须使用

return gameIDRef.transaction(t => {

    }).then(result => {

    }).catch(error => {

    });

但这是什么时候用的?这是@Renaud所指的回调版本吗?

function (error, committed, snapshot) {
});

Q2.第一种方式,当gameIDRef.transaction(t => {})被调用时,当game_id没有值和game_id没有值时会发生什么,我想告诉用户没有指定id的游戏。我怎么能做到呢?

return gameIDRef.transaction(t => {
      if(t === null)
        return; 
//Where does the control go from here? To then? 

// Also i have a conditional check in my logic, which is to make sure the numbers of players doesn't exceed the max_players before adding the player

t.players = where the existing players are stored
t.max_players = maximum number of players a game can have

if(t.players.length >= t.max_players)
// How do i send back a message to client saying that the game is full?
// Throw an error like this?
throw new functions.https.HttpsError('failed-condition', 'The game is already full');

    }).then(result => {

    }).catch(error => {
// and handle it here to send message to client?
    });
wh6knrhe

wh6knrhe1#

在您的云功能中有几个关键方面需要调整:
1.如云函数doc中所述,您需要使用promise来管理异步Firebase操作。在您的代码中,您使用了Transaction的回调“版本”。您应该使用“promise”版本,如下所示。
1.在事务中,您需要返回要写入数据库节点的新的所需状态。在您的问题的上一个版本中,您返回了您打算发送回Cloud Function调用者的对象:这是不正确的。
1.您需要将数据发送回客户端(即调用者),如下所示(即,以.then(r => {..})为单位)。
最后,要回答你在答案底部的问题(“当一个条件失败时,我如何发送回响应?如果返回undefined(即,您返回时不带参数),则事务将被中止,并且该位置处的数据将不会被修改”。
因此,基于上述情况,您可以认为以下内容应该有效。但是,您很快就会发现if (t === null)测试不起作用。这是因为第一次调用事务处理程序可能会返回null值,请参阅文档以及以下帖子:Firebase transaction api call current data is null和https://groups.google.com/forum/#!topic/firebase-talk/Lyb0KlPdQEo.

export const addUserToGame = functions.https.onCall((data, context) => {
    // Expected inputs - game_id(from data) and UID(from context)

    // ...

    return gameIDRef
        .transaction(t => {

            if (t === null) {  // DOES NOT WORK...
                return;
            } else {
                // your "complex" logic
                return { players: ..., max_players: xxx };
            }
        })
        .then(r => {
            return { response: 'OK' };
        })
        .catch(error => {
            // See https://firebase.google.com/docs/functions/callable?authuser=0#handle_errors
        })
});

你可以在我上面提到的SO帖子(Firebase transaction api call current data is null)中找到一个解决方法。我从来没有测试过它,我很想知道它是如何为你工作的,因为这个答案得到了很多选票。
另一个更激进的解决方案是使用Firestore,而不是实时数据库。使用Firestore Transactions,您可以很好地检查文档是否存在。

更新评论如下:

如果您希望根据事务中检查的内容向Cloud Function调用者发送回不同的响应,请使用下面的虚拟示例所示的变量。
不要在事务之外重新执行检查(即在then()中):只有在事务中,您才能确保如果另一个客户端在您的新值成功写入之前写入数据库节点,更新函数(即t)将被再次调用,并重试写入。

// ...
let result = "updated";
return gameIDRef
    .transaction(t => {
        console.log(t);
        if (t !== null && t.max_players > 20) {
            result = "not updated because t.max_players > 20"
            return;
        } else if (t !== null && t.max_players > 10) {
            result = "not updated because t.max_players > 10"
            return;
        }
        else {
            return { max_players: 3 };
        }
    })
    .then(r => {
        return { response: result };
    })
    .catch(error => {
        console.log(error);
        // See https://firebase.google.com/docs/functions/callable?authuser=0#handle_errors
    })
km0tfn4u

km0tfn4u2#

您正在尝试访问undefined变量的函数属性。在本例中,t没有属性players(您已经在上面的if语句中检查过了)。但是,在if语句之外,您仍然在访问t.players.update,并且由于t.players未定义,因此您会得到错误

ddarikpa

ddarikpa3#

对于可调用函数,您必须返回一个promise,该promise与要发送给客户端的数据对象一起解析。你不能只返回Promise链中的任何Promise。
在事务promise解析之后,您需要将另一个then回调链接到它,以返回您想要的值。一般结构为:

return child.transaction(t => {
    return { some: "data" }
})
.then(someData => {
   // transform the result into something to send to the client
   const response = convertDataToResponse(someData)
   return response
})
.catch(err => {
   // Decide what you want to return in case of error
})

请务必阅读交易的API文档。

相关问题