Skip to content

Sending and Receiving

Once a connection is OPEN, the day-to-day work is just two things: pushing messages out with send() and reacting to messages coming in through the message event. This lesson nails down both directions, plus the two rules that trip people up most — when you may send, and what order things arrive.

To send, you call send() with your payload:

ws.send('hello, server');
ws.send(JSON.stringify({ type: 'chat', text: 'hi' }));

send() accepts a string or binary data (we cover binary in the next lesson). It does not return a value and gives you no acknowledgement — it simply hands the message to the connection. A common pattern is to send structured data by serializing an object to JSON, then parsing it on the other end.

Incoming messages arrive as message events, and the payload is always on event.data:

ws.onmessage = (event) => {
const data = event.data; // a string (or binary, next lesson)
const msg = JSON.parse(data); // if the peer sent JSON
console.log(msg.type, msg.text);
};

event.data is the only place the payload lives. If you sent JSON, you parse it here; if you sent plain text, you use it directly. The handler runs once per message and may fire as often as the server has things to say.

This is the single rule worth tattooing on your memory: send() is only valid while readyState is OPEN.

  • Call send() while still CONNECTING and it throws an InvalidStateError — the connection is not ready.
  • Call send() after CLOSING or CLOSED and the data is silently dropped; it never reaches the peer.

So the safe pattern is to send from inside onopen, or to guard every call:

function safeSend(ws: WebSocket, data: string) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(data);
} else {
console.warn('not open — dropping or queuing message');
}
}

A WebSocket runs over a single TCP connection, and that buys you a strong, simple guarantee: messages are delivered in the order you sent them. If you call send('a') then send('b') then send('c'), the peer receives a, b, c — never reordered, never duplicated. The same holds for messages the server sends to you.

sequenceDiagram
  participant App as Your code
  participant WS as WebSocket
  participant Srv as Server
  Note over WS: readyState = OPEN
  App->>WS: send("a")
  App->>WS: send("b")
  WS->>Srv: a
  WS->>Srv: b
  Srv-->>WS: reply to a
  WS-->>App: message event (data = reply to a)
  Srv-->>WS: reply to b
  WS-->>App: message event (data = reply to b)
Sends and the message events they trigger, in order

What ordering does not promise is a request/response pairing. A WebSocket has no built-in notion of “this reply belongs to that send” — if you need that, you add your own correlation id inside the message. Ordering, yes; matching, that is your job.

The demo uses an in-page echo socket with the real WebSocket API, so it runs anywhere with no server. It sends three messages in a row from inside onopen and prints each echo as it returns — in order. In a real app only the first line would change to new WebSocket('wss://your-server').

JavaScript

Notice the first attempt — too early — is blocked because the socket is still CONNECTING. Then, once open, the three numbered messages come back as 1, 2, 3, in exactly the order they were sent. That is send(), the message event, the OPEN-only rule, and ordering, all in one run.

Where does the payload of an incoming message live?
What happens if you call send() while the socket is still CONNECTING?
Which guarantee does a WebSocket provide for messages?