An Introduction to WebSockets in JavaScript

WebSocket is an integral technology in many modern web applications. If you write code for the web, you’ve probably heard the term before, but maybe you’re not sure what exactly it is or how to use it. Fortunately, WebSocket isn’t a complex concept, and you can get a basic understanding of it pretty quickly.
What Is WebSocket?
WebSocket, unfortunately, is one of those names that doesn’t seem to make sense at first glance. WebSocket is actually the name of a communication protocol that allows for bidirectional communication between the client and the web server.
In simpler terms, WebSocket is a technology that allows a client and a server to create a connection where either party can send the other a message at any time.
This is different from a regular HTTP connection, where the client must initiate a request, and only then the server can send a response. In fact, WebSocket is a completely different communication protocol from HTTP that was designed to be HTTP-compatible. When a client application wants to initiate a WebSocket connection, it needs to use the HTTP upgrade mechanism to switch to the WebSocket protocol.
At this point, you might be thinking: “a protocol is just a set of rules, how can you use that to code?”.
The missing piece is something called a protocol stack. Essentially, devices that support a protocol have hardware and software built into them that allow you to write applications that communicate using the protocol. The protocol isn’t directly used to build anything.
Why Was WebSocket Created?
To illustrate the need for WebSocket, consider the mechanism behind chatting on the internet.
Someone sends a message to the chat server from their device, but the server still has to send that message to your device before you can read it. If the server uses HTTP, the server can’t directly forward that message to you, because the server can’t initiate requests.
There are a couple of ways to fix this problem with HTTP. One way is for the client to constantly send update requests to the server, and the server will forward any data it has in the response. This technique is called polling, and each request is called a poll. There are two variants of polling: long polling and short polling.
Using the long polling variant means that the client device is constantly asking the server whether any new messages are available. If new messages are available, the server will send the messages as a response. If not, the server would delay responding and hold open the connection until it had data to send back, and then the client would immediately make a new request.
This technique is inefficient, because HTTP wasn’t designed to be used this way. It performs adequately on a small scale, but each HTTP request involves sending extra data in the header, and it results in a significantly increased load on the server when many clients are polling it.
Here’s a diagram illustrating long polling:
The short polling variant is even less efficient. In short polling, the server doesn’t hold the connection open until there’s new data, meaning the client has to keep polling the server at fixed, very short intervals.
Another technique for bidirectional communication in HTTP is called streaming.
In streaming, after the first request is sent, the server holds the connection open indefinitely, sending new pieces of information as continuous partial responses to the client.
Using streaming results in a smaller data overhead and server load than polling, because ideally the client makes only one HTTP request. Unfortunately, streaming creates issues under certain conditions because browsers and network intermediaries(like proxies) often try to handle the partial responses as broken pieces of one large HTTP response (which is normal HTTP behavior), instead of as the separate messages they were intended to be.
WebSocket was created to solve these issues. Unlike HTTP, WebSocket was designed specifically for bidirectional communication. With WebSocket, once a connection is opened, the client and server can send messages back and forth without the issues of polling or streaming.
Use Cases for WebSocket
WebSocket is great, but that doesn’t mean it should be used everywhere.
Implementing WebSocket can add complexity to your application, especially on the server side, so it shouldn’t be done unless you have a good reason. That begs the question: what does a good reason look like?
WebSocket is ideal for use cases where frequent bidirectional communication at low latency is required. In other words, WebSocket provides an advantage for applications that need to communicate frequently or on a large scale. If the communication doesn’t need to be real-time or the application will never grow to a large scale, polling or streaming might be sufficient for use in that application.
Typical uses of WebSocket are in building chat applications, online multiplayer games, real-time collaboration and notification software, etc.
How to Use WebSocket on the Client Side
Using WebSocket on the server-side can be rather involved, and the process varies substantially depending on the language (like C#, Java, etc.) and library of choice, so we won’t cover it here. Next, we’ll briefly discuss how to use WebSocket on the client-side.
All modern browsers implement a web API called the WebSocket API, which is the browser’s protocol stack for the WebSocket protocol. You can use WebSocket in JavaScript using this API. The API allows you to create a WebSocket object, through which you create a WebSocket connection and interact with the WebSocket server.
You can use the following code format to create a WebSocket object:
let exampleSocket = new WebSocket("wss://www.example.com/socketserver", "dummyProtocol");
The first argument to the constructor is the URI of the WebSocket server you want to create a connection with. It will always start with “ws” or “wss”. The second argument is optional. Its value is either a string or an array of strings, which specifies the sub-protocols you support.
The WebSocket object has a read-only property called readyState. Accessing this property provides the current state of the WebSocket connection. readyState has four possible values: “connecting”, “open”, “closing”, and “closed”.
When that line of code runs, the browser will try and connect to the specified server. The connection won’t be completed at once, so the readyState of exampleSocket will be “connecting”. No messages can be sent or received until the connection is complete, at which point the value of readyState will become “open”.
The exampleSocket object has an event listener (which is different from DOM event listeners) called “onopen” that allows you to perform further actions only after the connection has been established. The object also has a “send” method that allows you to send strings, Blobs(binary data), and ArrayBuffers as messages to the server.
Here’s an example using these together:
exampleSocket.onopen = function (event) {
exampleSocket.send("WebSocket is really cool");
};
The API also provides a way for you to be able to react to the messages the server sends. This is done with the “onmessage” event listener. Here’s an example:
exampleSocket.onmessage = function (event) {
console.log(event.data);
}
Instead, you can also write an arrow function:
exampleSocket.onmessage = (event) => { console.log(event.data); }
The API also provides a close() method to close the connection. Here’s what it looks like:
exampleSocket.close();
WebSocket Enables Efficient Bidirectional Communication
WebSocket is a bidirectional communications protocol. Servers and browsers implement protocol stacks to communicate using WebSocket. WebSocket exists because HTTP wasn’t designed to be bidirectional. There are methods to implement bidirectional connections with HTTP, but they have issues.
WebSocket is powerful tech, but isn’t necessary in all cases, as it can significantly complicate application architecture. Using WebSocket on the client-side is done with the browser WebSocket API.
Read the full article here