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.
The four events
Section titled “The four events”Every browser WebSocket fires the same four events, no more:
open— the handshake succeeded; the connection is nowOPENand ready to use. This is the only safe place to begin sending.message— a message arrived from the peer. The payload is onevent.data.close— the connection has shut down. The event carries a numericevent.codeand aevent.reasonstring explaining why.error— something went wrong. It is informational and intentionally vague (for security); acloseevent 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));readyState: the four numbers
Section titled “readyState: the four numbers”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 progressWebSocket.OPEN; // 1 — ready to send and receiveWebSocket.CLOSING; // 2 — close handshake underwayWebSocket.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.
How events and states interlock
Section titled “How events and states interlock”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 --> [*]
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.
Watch the transitions live
Section titled “Watch the transitions live”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.
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.