Phoenix channel response vs push for error messages

I’m building a small multiplayer game. I have built the genserver for the game and am now implementing a phoenix channel to provide the play over websockets.

I am having difficulty with error handling. For example, the game requires a minimum number of players. I have created a “start_game” event handler using handle_in

def handle_in("start_game", _payload, socket) do
    "games:" <> game_name = socket.topic

    case GameServer.game_pid(game_name) do
      pid when is_pid(pid) ->
        summary = GameServer.start_game(game_name)

        case summary do
          {:error, reason} ->
            {:reply, {:error, %{reason: reason}}, socket}

          _ ->
            broadcast(socket, "game_summary", summary)
            {:noreply, socket}
        end

      nil ->
        {:reply, {:error, %{reason: "Game does not exist"}}, socket}
    end
  end

Attempting to start a game with less than 2 players the game genserver will return {:error, :insufficient_players}

My question is should this error be returned using a :reply tuple (as above), or would a push be better with the client listening for “game_error” events.

Basically, when should you use responses over pushes?

when should you use responses over pushes?

It depends :slightly_smiling_face:
Responses are good for synchronous communication. I guess in your particular scenario it’s fine and probably even desired to have client “blocked” waiting for the response. If it’s not - then do push.

The thing is if you already have established websocket channel - it’s more efficient to do request/response through it instead of regular HTTP request handled via Plug/Controller Assuming it fits your needs, say, you don’t need access to session and you have sorted out all authorization and other things that are more straight forward to do via HTTP.

Am I correct in saying that
{:reply, {:error, %{reason: "Game does not exist"}}, socket} is synchronous

and
broadcast(socket, "game_summary", summary) is asynchronous ?

Yes, that’s correct.

However, keep in mind that:
{:reply, {:error, %{reason: "Game does not exist"}}, socket} replies only to the client that pushed the message,
while
broadcast(socket, "game_summary", summary) will send that summary to all clients subscribed to "game_summary" topic.

2 Likes

Maybe you’ve already done so, but from an interaction standpoint, wouldn’t it make sense to communicate that threshold to users via the UI?

For example, a disabled button with placeholder text “Waiting for X more player(s)” or “Waiting for another player” that transitions into an enabled/clickable “Start Game” button.

It wouldn’t cover edge cases where players drops off last minute, but it’d probably prevent the error entirely for the majority of cases.

You are right, the UI will mitigate against errors like this from happening, but as the app grows, I’m pretty sure I’ll have to handle these responses in the future.

1 Like