簡體   English   中英

Nodejs Firebase 事務 - 如果條件失敗,更新節點並發送錯誤

[英]Nodejs Firebase Transaction - Update a node and send error if a condition fails

該場景是當玩家在應用程序中點擊“加入游戲”時,會調用雲 function 將玩家添加到 firebase 數據庫,並將結果作為響應傳遞。

數據庫結構:

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

雲 function 應該檢查 /players 的大小是否小於 /max_players,只有這樣,它才應該使用播放器的 uid 更新 /players 並返回響應。

這是我的雲 function:

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

您正在嘗試訪問undefined變量的 function 屬性。 在這種情況下, t沒有屬性players (您已經在上面的 if 語句中檢查了它)。 但是,在您的 if 語句之外,您仍在訪問t.players.update ,並且由於 t.players 未定義,您會收到錯誤

在您的雲 Function 中有幾個關鍵方面需要適應:

  1. 如 Cloud Functions 文檔中所述,您需要使用Promise 管理異步 Firebase 操作。 在您的代碼中,您使用Transaction的回調“版本”。 您應該使用“promise”版本,如下所示。
  2. 在事務中,您需要返回您想要寫入數據庫節點的新的所需 state。 在您的問題的先前版本中,您返回 object 您打算發送回雲 Function 調用方:這是不正確的。
  3. 當事務返回的 promise 被解析時,您需要將數據發送回客戶端(即調用方),如下圖所示(即在.then(r => {..})中)。

最后,在答案底部回答您的問題(“當條件失敗時,我如何發回響應?”)。 只是return; 如文檔中所述,“如果返回未定義(即您不帶參數返回),則事務將被中止,並且不會修改此位置的數據”。

因此,基於上述內容,您可以認為以下內容應該有效。 但是,您很快就會發現if (t === null)測試不起作用。 This is because the first invocation of a transaction handler may return a null value, see the documentation , as well as the following posts: Firebase transaction api call current data is null and https://groups.google.com/forum/#!主題/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 事務 api 調用當前數據為 null )。 我從來沒有測試過它,我很想知道它是如何為你工作的,因為這個答案得到了很多選票。


另一個更激進的解決方法是使用 Firestore,而不是實時數據庫。 使用 Firestore Transactions ,您可以很好地檢查文檔是否存在。


更新在評論之后:

如果您想根據事務中檢查的內容向 Cloud Function 調用方發回不同的響應,請使用如下虛擬示例所示的變量。

不要在事務之外重新執行檢查(即在then()中):只有在事務中你才能確定如果另一個客戶端在你的新值成功寫入之前寫入數據庫節點,更新 function (即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
    })

使用可調用函數,您必須返回一個 promise,它使用數據 object 解析以發送到客戶端。 您不能只從 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 文檔以了解事務

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM