JavaScript WebSocket Table

PieceWhat it doesField note
new WebSocket(url)Open a socketws:// or wss:// (secure) - HTTP cannot upgrade to plain ws from HTTPS pages
onopen / oncloseLifecycleclose event fires on server drops AND errors - reconnect logic lives there
send(data)Push either wayStrings or ArrayBuffers - full-duplex, no request/response pairing
onmessageReceive pushesServer can initiate - the property fetch lacks
binaryType = 'arraybuffer'Binary framesDefault blob; arraybuffer for typed-array parsing
readyState0-3 statesCONNECTING before onopen - sending early throws
heartbeat ping/pongKeep-alive detectionDead TCP connections need app-level pings - sockets lie silently
reconnect with backoffThe missing built-inNo auto-reconnect - exponential backoff is YOUR code
Reference: the MDN WebSockets API reference. A WebSocket is a full-duplex socket after one HTTP upgrade handshake: either side pushes at any time, which is the property request/response lacks and realtime apps need. The API is deliberately thin - open, message, close, send - so the REAL engineering is in the parts it omits: readyState discipline (send before onopen throws), app-level heartbeats (dead TCP connections look alive until you try to write), and reconnect-with-backoff, which every production socket app writes by hand. Bottom line: wss:// always (mixed-content blocks plain ws), binary frames arrive per binaryType, and the socket lifecycle outlives your UI state - re-sync on reconnect. Related tools: typed arrays table (binary frame parsing), async/await table (promisifying the connect dance), and fetch table (the handshake that upgrades).

A WebSocket is a full-duplex socket reached through one HTTP upgrade handshake: after onopen, either side pushes at any time with no request/response pairing. That single property - server-initiated pushes - is what realtime apps need and what fetch structurally cannot do.

Bottom line: the API is deliberately thin (open, message, close, send), so the real engineering lives in what it omits: send before onopen throws (readyState discipline), dead TCP connections LOOK alive until a write fails (heartbeat pings), and there is NO auto-reconnect - every production socket app hand-writes exponential backoff.

The honest part: a WebSocket's lifecycle outlives your UI state. The reconnect that succeeds hands you a socket with no memory - server-side session state, missed messages, and client-side UI all need an explicit re-sync step, or the app shows a live connection over stale data.

How to use

  1. Gate every send: wrap socket.send in a readyState check or queue outgoing messages until onopen - CONNECTING sockets throw, and users click fast.
  2. Heartbeat to detect silent deaths: a 30-second ping from the client; two missed pongs = force close and reconnect - NAT gateways kill idle sockets without warning.
  3. Reconnect with exponential backoff and jitter: 1s, 2s, 4s, capped at 30s plus random jitter - thundering-herd reconnections after a server restart are a self-inflicted outage.

Frequently asked questions

Why do I need WebSockets when fetch and polling exist?

Direction and latency. Polling asks 'anything new?' on a timer - the updates arrive up to one interval late while paying a full HTTP request each tick. Long-polling improves latency but holds request slots. A WebSocket is a standing pipe: the server pushes the instant data exists, with one handshake of overhead and frame-sized messages after. The decision line: request/response data (forms, page loads) stays fetch; state that changes server-side and matters within seconds (chat, collaborative edits, live prices, game state) wants a socket. SSE (server-sent events) is the middle option when pushes are one-directional - simpler, but the client cannot talk back on the same pipe.

Why does my WebSocket connection die silently after a few idle minutes?

Intermediate infrastructure. NAT gateways, load balancers and firewalls hold connection state with idle timeouts - an open socket with no traffic looks dead to them and gets dropped WITHOUT notifying either end, while the TCP connection on both sides still appears established. The fix is application-level heartbeats: the client pings every 30 seconds (any tiny frame works), treats two missed pongs as a dead socket, and force-closes to trigger reconnect. The lesson generalized: absence of error is not proof of liveness on long-lived connections - liveness is something you probe for.

What belongs in my reconnect logic?

Exponential backoff with jitter, a re-sync step, and state gating. Backoff: retry at 1s, 2s, 4s... capped around 30s, with random jitter so a thousand clients do not stampede the recovering server on the same tick. Re-sync: after onopen, treat it as a NEW session - request missed data (last event id, current state snapshot) before trusting the UI, because messages sent while you were disconnected are gone unless the server queues them. State gating: show a reconnecting indicator and freeze optimistic UI until re-sync completes - a live-looking connection over stale data is worse than an honest offline badge. And cap it: after N failures, back off to manual ('reconnect' button) territory.

How do binary frames work, and when do they beat JSON strings?

The socket transports strings or bytes; binaryType decides how onmessage hands you the bytes ('blob' by default, 'arraybuffer' for typed-array parsing). Binary wins when payloads are dense and structured: game state at 60Hz, sensor streams, audio chunks - a Float32Array frame is smaller to send and faster to parse than the equivalent JSON (no stringify/parse, no key repetition). The costs: no human readability in devtools without tooling, and you own the schema (field order, endianness - DataView territory). The pragmatic split: JSON for control messages and low-frequency data, binary for the high-frequency stream that pays for the complexity.

Related tools