Skip to content

Events and readyState

A WebSocket is an event-driven object. Your code does not poll it or block on it; instead you attach handlers and let the connection call you back when something happens. There are exactly four events, and a single field — readyState — that always tells you where you are. Master these and the API holds no surprises.

Every browser WebSocket fires the same four events, no more:

  • open — the handshake succeeded; the connection is now OPEN and ready to use. This is the only safe place to begin sending.
  • message — a message arrived from the peer. The payload is on event.data.
  • close — the connection has shut down. The event carries a numeric event.code and a event.reason string explaining why.
  • error — something went wrong. It is informational and intentionally vague (for security); a close event almost always follows.

You can attach handlers two ways. The on* properties are the quickest:

const ws = new WebSocket('wss://example.com');
ws.onopen = () => console.log('open');
ws.onmessage = (event) => console.log('data:', event.data);
ws.onerror = () => console.log('error');
ws.onclose = (event) => console.log('closed', event.code, event.reason);

Or addEventListener, which lets you attach more than one handler per event:

ws.addEventListener('message', (event) => console.log(event.data));

While the events tell you when things change, readyState tells you the current state at any instant. It is a number, and the class exposes named constants for each value:

WebSocket.CONNECTING; // 0 — handshake in progress
WebSocket.OPEN; // 1 — ready to send and receive
WebSocket.CLOSING; // 2 — close handshake underway
WebSocket.CLOSED; // 3 — finished, or never opened
if (ws.readyState === WebSocket.OPEN) {
ws.send('safe to send now');
}

The two views line up neatly: the open event fires as you enter state 1, and the close event fires as you enter state 3. Checking ws.readyState === WebSocket.OPEN before sending is the standard guard against the “I tried to send too early or too late” bug.

This is the same lifecycle from lesson one, now annotated with the event that fires on each transition:

stateDiagram-v2
  [*] --> CONNECTING: new WebSocket()
  CONNECTING --> OPEN: open event
  CONNECTING --> CLOSED: error then close event
  OPEN --> CLOSING: close() / peer closes
  CLOSING --> CLOSED: close event
  OPEN --> CLOSED: drop, then close event
  note right of OPEN: message events may fire repeatedly here
  CLOSED --> [*]
States on the nodes, events on the transitions

The message event is special: it is not a transition. It can fire any number of times while you sit in OPEN, which is exactly the point of a persistent connection.

The demo uses an in-page echo socket with the real WebSocket API, so it runs anywhere with no server. It logs readyState at every step using the named constants. In a real app only the first line — new WebSocket('wss://your-server') — would differ.

JavaScript

Trace the output against the diagram: CONNECTING at construction, OPEN in the open handler, still OPEN when the message arrives, CLOSING the moment close() is called, and CLOSED by the time the close event fires — carrying the code 1000 and reason we passed.

Which event can fire many times during a single connection?
What numeric value does WebSocket.OPEN correspond to?
Where do you find out why a connection ended?