Welcome to a tutorial on how to do AJAX long polling. So you want to run a real-time application, but web sockets are too complicated? Or maybe you have heard of this long polling thing from somewhere and wondering what it does?
To do AJAX long polling, set an AJAX function to repeat itself:
var poll = () => {
fetch("SCRIPT.PHP")
.then(res => res.text())
.then(txt => { /* DO SOMETHING */ poll(); })
.catch(err => poll());
};
poll();
This is extremely useful for processes that require “live updates” – Live chat, live game scores, live news, etc… Let us walk through a simple example of long-polling a game score update in this guide, read on!
TABLE OF CONTENTS
DOWNLOAD & NOTES
Here is the download link to the example code, so you don’t have to copy-paste everything.
EXAMPLE CODE DOWNLOAD
Just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.
SORRY FOR THE ADS...
But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.
Buy Me A Coffee Code Boxx eBooks
AJAX LONG POLLING WITH PHP MYSQL
All right, let us now get into an example of AJAX long polling a live game score using PHP MySQL.
TUTORIAL VIDEO
PART 1) GAME SCORE TABLE
CREATE TABLE `score` (
`time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`home` int(3) NOT NULL,
`away` int(3) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `score`
ADD PRIMARY KEY (`time`);
For a start, this simple table will store the game scores.
time
Time at which the score is recorded. Primary key and defaults to the current time.home
The score for the home team.away
The score for the away team.
PART 2) SERVER-SIDE PHP
<?php
class Score {
// (A) CONSTRUCTOR - CONNECT TO DATABASE
protected $pdo = null;
protected $stmt = null;
function __construct () {
$this->pdo = new PDO(
"mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET,
DB_USER, DB_PASSWORD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
}
// (B) DESTRUCTOR - CLOSE CONNECTION
function __destruct () {
if ($this->stmt !== null) { $this->stmt = null; }
if ($this->pdo !== null) { $this->pdo = null; }
}
// (C) GET LATEST SCORE
function getScore () {
$this->stmt = $this->pdo->prepare(
"SELECT *, UNIX_TIMESTAMP(`time`) AS `unix`
FROM `score` ORDER BY `time` DESC LIMIT 1"
);
$this->stmt->execute();
return $this->stmt->fetch();
}
}
// (D) DATABASE SETTINGS - CHANGE THESE TO YOUR OWN!
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8mb4");
define("DB_USER", "root");
define("DB_PASSWORD", "");
// (E) CHECK FOR SCORE UPDATES
if (isset($_POST["last"])) {
// (E1) SET TIME LIMIT
set_time_limit(30); // set an appropriate time limit
ignore_user_abort(false); // stop when long polling breaks
// (E2) LOOP UNTIL THERE ARE UPDATES OR TIMEOUT
$_SCORE = new Score();
while (true) {
$score = $_SCORE->getScore();
if (isset($score["unix"]) && $score["unix"] > $_POST["last"]) {
echo json_encode($score);
break;
}
sleep(1); // short pause to not break server
}
}
The PHP script looks complicated, but keep calm and look carefully:
- (A, B, E2) When
$_SCORE = new Score()
is created, the constructor will connect to the database. The destructor closes the connection. - (D) Remember to change the database settings to your own.
- (E) The HTML page will send
$_POST["last"]
over, a Unix timestamp of the last score update it got. - (E1, E2, C) This part then fetches the latest score from the database in a loop. Respond only when there is an updated score.
PART 3) CLIENT-SIDE HTML JAVASCRIPT
<!-- (A) HTML SCOREBOARD -->
<div id="sTime"></div>
<div id="sBoard">
<div id="sHome"></div>
<div id="sAway"></div>
<div id="sHomeT">Home</div>
<div id="sAwayT">Away</div>
</div>
<script>
// (B) LAST UPDATED TIMESTAMP
var last = 0;
// (C) AJAX LONG POLL
function poll () {
// (C1) FORM DATA
let data = new FormData();
data.append("last", last);
console.log("Fetch run", last);
// (C2) FETCH UPDATE ON SERVER RESPONSE
fetch("2-score.php", { method:"POST", body:data })
.then(res => res.json())
.then(data => {
// (C2-1) UPDATE HTML DISPLAY
document.getElementById("sTime").innerHTML = data.time;
document.getElementById("sHome").innerHTML = data.home;
document.getElementById("sAway").innerHTML = data.away;
// (C2-2) NEXT ROUND
last = data.unix;
poll();
})
// (C3) CATCH ERROR - LOOP ON TIMEOUT
.catch(err => poll());
}
// (D) GO!
window.onload = poll;
</script>
As in the introduction, this does an AJAX request that is set to loop itself infinitely to check for updates from the server.
- HTML “scoreboard” to display the scores.
<div id="sTime">
The time when the score update is received.<div id="sHome">
Home team score.<div id="sAway">
Away team score.
var last
is used to hold the Unix timestamp of the current score update.poll()
Will sendlast
to the server to check for score updates.- (C2 & C3) Notice how it loops itself endlessly to keep on getting “live updates”.
EXTRAS
That’s all for this guide, and here is a small section on some extras and links that may be useful to you.
CHANGING THE TIMEOUT LIMIT FOR FETCH
// (A) NEW ABORT CONTROLLER - 5 SECONDS
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
// (B) FETCH WITH ABORT CONTROLLER
fetch(url, { signal: controller.signal })
.then(res => res.text())
.then(txt => { ... });
- Please take note that at the time of writing, the timeout limit for
fetch()
depends on the browser. - Chrome seems to be 300 seconds and Firefox is 90 seconds.
- The “official way” to set the timeout for
fetch()
is seeming anAbortController
. Credits to this post on Stack Overflow.
LIMITATIONS – LONG POLLING VS WEB SOCKET
Congratulations code ninja, you have mastered the arts of long polling. But there is one thing you need to understand – It is not a replacement for web sockets… Not even close. In a nutshell:
- An instance of the PHP server script will run for every client connection. If there are 1000 connections, the server will spawn 1000 instances.
- But when it comes to web sockets, we only create 1 instance of the server script to serve 1000 connections.
So yeah. Long polling may be a shortcut fix for servers that are not too busy, but it is a far cry from a true solution for a real-time system. Go invest some of your time in learning web sockets if you are interested.
PUSH NOTIFICATIONS
Also, times have changed. We no longer live in the Stone Age of the Internet, and there is something called “push notification”. Yep, this makes a lot more sense when pushing out updates and news. Check out the links below for more examples of push and WebSockets.
LINKS & REFERENCES
- PHP Push Notifications – Code Boxx
- Add push notifications to a web app – Google Developer
- PHP Live Chat – Code Boxx
- NodeJS Live Chat – Code Boxx
THE END
Thank you for reading, and we have come to the end of this guide. I hope that it has helped you with your project, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!
This worked great for what you designed it for, however, how does on display multiple rows rather than one?
I want to show the last status of each user, let’s say there are 10 users total, each have a variety of status updates, but I just want to display the last status per unique user?
https://code-boxx.com/json-php-javascript-beginners/