
Long-Polling Explained With An Example
Web applications were initially developed on the model that a client is always the initiator of transactions. That means that a client would always make a request to the server to retrieve some data, and only then would the server respond with the requested information. As a result, the traditional model does not have a mechanism for the server to independently send or push data to the client without an initial request.
Web Sockets solve this problem by allowing the client to directly connect, and listen, to a port on the server. So when the server has information it wants the client to know about, it can emit the information via the socket and any subscribed clients will receive the information without ever having to make the initial request. Arguably, Web Sockets are optimised for CPU usage, memory usage and bandwidth usage.
However, beyond the land of Evergreen browsers, support for the Web Sockets API may be sketchy. This is typically why many Web-Socket-orientated architectures, such as Google Firebase, rely on Long-Polling as a fall-back.
Long-Polling works on top of the traditional client-server model. As previously mentioned, the client makes the initial request. But instead of responding to the request as soon as possible, the server will keep the connection open until there is new and relevant data to be returned. If new data is available immediately, the server can respond immediately as it does in the traditional request/response model. Once the server responds to the client successfully, the client then immediately makes another request for which the server may hold the connection open and respond as appropriate. This process continues until the client application is closed and the requests to the server end.
There is a quirk to this approach though. If a connection is held open for too long, the client may timeout the request. In most modern browsers, this timeout lasts around 120 seconds to 360 seconds. Though this is not as straight forward to work around since the server has no idea when a connection is dropped specifically due to a timeout - the client application can be designed to handle this situation by making a new request to the server.
Whether a system uses Web Sockets, Long-Polling, or a combination (Long-Polling is often implemented as a fall-back to Web Sockets) there are some considerations that need to be taken:
- As usage of the service grows, how will the real-time backend be orchestrated
- When mobile devices rapidly switch between WiFi and Cellular networks - or lose connections - how will connections be re-established?
- How can missed messages be handled? What about messages which are sent/recieved in the wrong order?
- Load balancing, especially when using stateful backends
Regardless of whether Long-Polling is frowned upon or not, it's a useful concept to understand - and in the right cases - used to solve a software problem. So, here's an outline of Long-Polling in action with VueJS and NodeJS.
Long-Polling example with VueJS and NodeJS
If you just want the code, it's available on GitHub here.
Step 1: We'll start by setting up the server. Create a new directory and run the command npm init -y
. This will create a package.json
file populated with default values, and then create a `server.js` file in the root of the directory.
Next, install ExpressJS required for the NodeJS server using the command:
npm install express --save
Step 2: Add the following code to the server.js
file to configure the server:
var http = require('http');
var path = require('path');
var express = require('express');
var router = express();
var server = http.createServer(router);
router.use('/client', express.static(path.resolve(__dirname, 'client')));
router.get('/data', function(req, res, next) {
return res.json("hello world!");
});
server.listen(process.env.PORT || 3000, process.env.IP || "0.0.0.0", function() {
var addr = server.address();
console.log("Server listening at", addr.address + ":" + addr.port);
});
In the code above, four libraries (http
, path
, express
and router
) are imported to the file. A HTTP
server is then instantiated with the router
.
Next, a static directory is declared. The endpoint /client
is configured to serve the client
directory in our project (covered later) which will contain our VueJS front-end.
An endpoint /data
is defined using the router.get()
method, and for now it returns a JSON response of hello world!.
Finally the server is set to run on localhost port 3000 by default, or any other port set in the environment
file.
After saving this file, you can start the server by running the command:
node server.js
If you then navigate to http://localhost:3000/data
in your browser hello world will be seen printed to the screen.
Step 3: Now the function which is bound to the /data
endpoint is modified to simulate live data. A random number generator can be used to determine how often new data is available. When a request is made, the connection will be held for a random number of seconds (based on the random number generator) before returning the time in milliseconds.
The response object can simply be the value of the time in milliseconds, but to make things a bit more interesting and a better simulation of a real system, sometimes a new response value will not be available. And to avoid the connection timing out, the connection is closed and a flag called hasValue
is set to false. The front-end system will use this flag to handle and determine if the data returned from long-polling deems a UI repaint.
router.get('/data', function(req, res, next) {
const time = (new Date()).getTime();
let seconds = Math.random() * 10000;
if (seconds < 1000) {
return res.json({hasValue: false, value: null});
}
if (seconds > 8000) {
seconds = 60000;
}
console.log("waiting seconds before responding", seconds);
return setTimeout(function () {
return res.json({hasValue: true, value: time});
}, seconds);
});
At this point, you can still open http://localhost:3000/data
in your browser but instead of seeing the earlier demo text of hello world, you will instead see a number. If you refresh this screen multiple times, you should notice that each call takes a variable amount of time. Sometimes it will resolve immediately, and sometimes it will take a few seconds before showing you the response data.
This is how we are 'mocking' the API to simulate a real system where web sockets and/or long-polling is appropriate to get real-time data as it is updated.
Step 4: With the example backend working as expected, we can move to the front-end which will sit within the /client
directory. To save the hassle of working with jQuery, the front-end will use a CDN-hosted version of VueJS. Create an index.html
file within the /client
directory like so:
<html>
<head>
<title>Long Polling Example</title>
</head>
<body>
<div id="long-polling-app">
<div>
<h1>Long Polling Example</h1>
</div>
<div>
<table>
<tr>
<th>Responses</th>
</tr>
<tr>
<td>
No responses!
</td>
</tr>
</table>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-resource@1.5.1"></script>
</body>
</html>
A simple screen is now defined, which displays a table with just one row. Just before the closing <body>
tag a <script>
tag is used to import the VueJS library and the Vue Resource library to make HTTP calls a piece of cake.
This screen can be accessed, as defined in server.js
as a static directory, at http://localhost:3000/client
.
Step 5: The next step is to define the logic, using VueJS, which will call the /data
endpoint and display the response in the table as it arrives. This will allow the user to 'watch' the data on the screen in real-time… Just like web sockets!
To do this, create a new inline <script>
tag after the Vue Resource import and paste the following code:
var app = new Vue({
el: '#long-polling-app',
data: {
responses: []
},
created: function () {
this.getData();
},
methods: {
getData: function () {
this.requests.push((new Date().getTime()));
return this.$http.get('http://localhost:3000/data')
.then(response => {
if (response.body.hasValue === true) {
this.responses.push(response.body.value);
return;
}
return;
})
.then(function () {
return this.getData();
})
}
}
});
The above snippet declares a Vue element named long-polling-app
with one property called responses
which is an Array. A getData()
function is declared which makes a request to the /data
endpoint. When it receives a response from the server, it checks if hasValue
is true. If so, it will add the value
(the time in milliseconds) to the responses
array, and then call itself, getData()
, again.
The created()
function executes as soon as the app has loaded and bootstrapped itself in the browser. In this example, the created()
function calls the getData()
function immediately.
That means that as soon as the page loads, the app will automatically initiate the long-polling process. Every time the request is made the server will respond to the request when data is available, the client will need to update the screen accordingly, and immediately make a the succeeding request. In very simple terms, this is long-polling explained as an example.
A call to getData()
is also included in .then()
to re-initiate a call if it fails for any reason. This is sufficient for this basic example - however in a real application, a failed request will need to be handled more elegantly. For example, a HTTP 404 response is unlikely to be resolved by making the call again so soon.
Step 6: The last step is the easiest. The VueJS script needs to be bound to the HTML so that the user can get visual feedback as the long-polling process runs in the background.
Simply modify the table-row in the HTML to display the responses
array:
<tr v-for="response in responses">
<td>
{{response}}
</td>
</tr>
Now, VueJS will do all the magic of updating the DOM in the browser. Here's a breakdown of what's happening:
- The page is loaded at
http://localhost:3000/client/index.html
- VueJS bootstraps, and the
created()
function initiates the firstgetData()
call to the/data
endpoint - The function bound to the
/data
endpoint inserver.js
uses a random number generator to decide how long to keep the connection open for. This simulates the availability of new real-time data - In some instances, the
hasValue
flag is set tofalse
to simulate a connection being successfully resolved to avoid a HTTP timeout - If there is new data available, the connection is resolved with the
hasValue
flag set totrue
- The VueJS receives the response and updates the screen as necessary (in this case add another row to the table)
- Immediately after receiving the response, the VueJS client makes the next request
- This cycle continues indefinitely until the connection with the server is unexpectedly closed, or the browser window is closed

Thanks for reading!
My name is Zahid Mahmood, and I'm one of the founders of Anterior. I started this technology blog when I was in high school and grew it to over 100,000 readers before becoming occupied with other projects. I've recently started writing again and will be posting more frequently.