WebSockets Explained: Real-Time Data Without Polling

2026-11-07来源 dev.toEnglish

A customer adds a ceramic mug to their basket on a small shop's website, then waits. The order page says Processing, and nothing changes until they refresh. The server knew the order shipped ten minutes ago, but the page had no way of hearing about it. The usual fix is polling: the page asks the server "anything new?" every few seconds. Most of those requests get the answer "no", yet each one opens and closes a connection for nothing. WebSockets exist for exactly this gap. One connection stays open, the server pushes a message the moment something changes, and the page updates without asking again. The question is whether your page needs a live wire at all.

A WebSocket starts as an ordinary HTTP request that asks the server to upgrade the connection. If the server agrees, that same connection stays open for two-way messages, and either side can send at any time. That is the whole idea. The rest of this post is about deciding when that open connection earns its keep, and what breaks when you treat it like a normal request.

What I would check before using it

My judgement, from years of building small web apps, is this: reach for a WebSocket when data changes at unpredictable times and the user needs to know within a second: a live order status, a chat message, a price that moves while someone watches it. Those are the honest cases. If the data changes on a schedule the page already knows, polling on that schedule is simpler and easier to debug. If the flow is one way, server to browser only, look at server-sent events first: they run over plain HTTP, they reconnect on their own, and they pass through proxies that block WebSockets.

I would check four things before committing. First, connection count. Every open WebSocket is a connection your server holds, so a thousand idle shoppers are a thousand open sockets. That is fine for Node and a problem for a server built around one thread per request. Second, the network in between. Some corporate proxies and old load balancers drop long-lived connections or refuse the upgrade, so test through the path your users actually take. Third, what happens when the connection drops. Wi-Fi drops, phones switch networks, servers restart, and the browser does not reconnect on its own. Your code must notice the close event and connect again, or the page goes stale. Fourth, security. Use wss, the encrypted version, in production, and check the login during the opening handshake, not in the first message, because by then the socket is already open.

The verdict, in short: use WebSockets for live, two-way updates where a second of delay matters. Do not use them for data on a schedule, for one-way feeds, or on a server that cannot hold many open connections. If any of those flip, polling or server-sent events win.

A live price ticker, end to end

The worked example is the shop from the opening. Three products have prices that move through the day, and a dashboard page should show each new price the moment it changes. A small Node.js server holds the prices and pushes an update every half second. A client connects, subscribes, and prints each update as it arrives. The server uses the ws package, the standard WebSocket library for Node. Here is the server:

const { WebSocketServer } = require('ws'); const PRODUCTS = [ { id: 'SKU-101', name: 'Ceramic mug', price: 18.00 }, { id: 'SKU-102', name: 'Linen tote', price: 24.50 }, { id: 'SKU-103', name: 'Desk lamp', price: 42.00 }, ]; const wss = new WebSocketServer({ port: 8080 }); wss.on('connection', (socket) => { console.log('client connected'); let tick = 0; const timer = setInterval(() => { const product = PRODUCTS[tick % PRODUCTS.length]; const change = (tick % 2 === 0 ? 0.25 : -0.10); product.price = Math.round((product.price + change) * 100) / 100; socket.send(JSON.stringify({ type: 'price', id: product.id, name: product.name, price: product.price, at: new Date().toISOString(), })); tick += 1; if (tick >= 6) { clearInterval(timer); socket.close(); wss.close(); } }, 500); }); console.log('price ticker running on ws://localhost:8080'); 

The price changes are deliberately boring, up 25 cents, then down 10 cents, alternating, so the output is easy to check by hand. The server sends six updates and then closes the connection. Here is the client:

const WebSocket = require('ws'); const socket = new WebSocket('ws://localhost:8080'); socket.on('open', () => { console.log('connected to the ticker'); socket.send(JSON.stringify({ type: 'subscribe', ids: ['SKU-101', 'SKU-102', 'SKU-103'] })); }); socket.on('message', (data) => { const update = JSON.parse(data.toString()); console.log(update.id, update.name, 'now $' + update.price.toFixed(2)); }); socket.on('close', () => { console.log('ticker closed the connection'); }); 

This is the real output from running both, server first, then the client:

connected to the ticker SKU-101 Ceramic mug now $18.25 SKU-102 Linen tote now $24.40 SKU-103 Desk lamp now $42.25 SKU-101 Ceramic mug now $18.15 SKU-102 Linen tote now $24.65 SKU-103 Desk lamp now $42.15 ticker closed the connection 

Every price matches the arithmetic. The mug starts at 18.00, goes up 25 cents to 18.25, then down 10 cents to 18.15. The tote starts at 24.50, down 10 cents to 24.40, then up 25 cents to 24.65. The lamp starts at 42.00, up to 42.25, then down to 42.15. Nothing was polled. The client asked for nothing after subscribing, and each price arrived about half a second after the last.

Now the part the tidy tutorials skip: something goes wrong. A natural first version of the client sends the subscribe message right after creating the socket, before the connection is open:

const socket = new WebSocket('ws://localhost:8080'); // BUG: sending immediately, before the connection is open. socket.send(JSON.stringify({ type: 'subscribe', ids: ['SKU-101'] })); 

Creating a WebSocket does not open it. The connection takes a moment, and sending during that moment fails at once. The real error from running this version is:

Error: WebSocket is not open: readyState 0 (CONNECTING) at WebSocket.send (.../node_modules/ws/lib/websocket.js:470:13) at Object.<anonymous> (.../client-broken.js:6:8) 

The process exits with code 1 and never connects at all. The fix is the client shown above: send only inside the open handler, which runs once the handshake has finished. If you need to send from elsewhere, check socket.readyState === WebSocket.OPEN first, or queue the message until open fires. The rule is simple once you have seen the error: a WebSocket is a connection you wait for, not a socket you can use the instant you create it.

Common mistakes and how to fix them

1. Sending before the connection is open. The symptom is Error: WebSocket is not open: readyState 0 (CONNECTING), shown above. The fix is to send inside the open handler, or check readyState before every send.

2. Forgetting the error handler. A client with no error listener that connects to a server that is not running crashes the whole process. Running one gave an unhandled error event, an AggregateError [ECONNREFUSED], and exit code 1. The fix is to always attach socket.on('error', ...) and treat a refused connection as a signal to retry later, not as a crash.

3. Parsing every message as JSON without a guard. One malformed message kills the client. When a server sent the text this is not json{, the unguarded JSON.parse threw SyntaxError: Unexpected token 'h', "this is not json{" is not valid JSON and the process exited. The fix is a try/catch around JSON.parse in the message handler, so a bad message is logged and ignored while the connection keeps running.

Where this leaves you

A WebSocket is one connection that stays open so the server can speak first. That small idea has a real cost: connections to hold, drops to recover from, and a handshake to secure. Use it when a page must learn about a change the moment it happens, and reach for polling or server-sent events when the data is predictable or one way. The next step is to take the ticker above, run it on your own machine, and then break it on purpose: stop the server mid-run and add a reconnect that waits a second and tries again. That reconnect is what makes a live page trustworthy.

Further reading

Originally published at https://practicaldevblog.blogspot.com/2026/11/websockets-explained-real-time-data.html

原文:https://www.a8q9.cn/en/article/websockets-explained-real-time-data-without-polling-0cdbff.html

相关文章