C.W.K.
Stream
Lesson 02 of 07 · published

Request-Response over WebSocket

~13 min · protocol, correlation-id, promises

Level 0Poller
0 XP0/60 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

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.

Code

Promise-wrapped request·javascript
const pending = new Map(); // id -> { resolve, reject, timer }

function request(ws, type, data, timeoutMs = 10_000) {
  return new Promise((resolve, reject) => {
    const id = crypto.randomUUID();
    const timer = setTimeout(() => {
      pending.delete(id);
      reject(new Error(`timeout: ${type}`));
    }, timeoutMs);
    pending.set(id, { resolve, reject, timer });
    ws.send(JSON.stringify({ id, type, data }));
  });
}

ws.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  if (msg.id && pending.has(msg.id)) {
    const entry = pending.get(msg.id);
    pending.delete(msg.id);
    clearTimeout(entry.timer);
    if (msg.type === 'error') entry.reject(new Error(msg.message));
    else                      entry.resolve(msg.data);
    return;
  }
  // Unsolicited event — push, not response
  handleEvent(msg);
};

// Usage
const user = await request(ws, 'user.get', { id: '123' });
Server side — echo the id·python
async def handle(ws, msg):
    rid = msg.get('id')
    typ = msg.get('type')
    data = msg.get('data', {})
    try:
        result = await dispatch(typ, data)
        await ws.send_json({
            'id': rid,
            'type': f'{typ}.response',
            'data': result,
        })
    except Exception as e:
        await ws.send_json({
            'id': rid,
            'type': 'error',
            'code': type(e).__name__,
            'message': str(e),
        })

External links

Exercise

Implement request() and a server side that responds with the echoed id. Test five concurrent requests; confirm responses arrive out-of-order and are matched correctly. Now make the server hold one request for 15 seconds — confirm the client times out at 10s while the others still resolve normally.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.