WebSocket is fire-and-forget by default
The protocol does not give you "wait for the response to this specific message." Every message stands alone. To get request-response semantics — useful for "fetch user data," "save preferences," anything where the client wants confirmation — you add a correlation ID: a unique ID the client picks, the server echoes back, and the client uses to match response to request.
Promise-wrapped requests
The client-side ergonomic is a small Promise wrapper. await ws.request('user.get', {id: 'x'}) returns the response data. Internally: pick a UUID, register a resolver, send, wait. Add a timeout because no response is also a possible outcome.