Progress Bar with AJAX and PHP: From Polling to Streams
My 2015 Stack Overflow question about real-time progress bars used AJAX polling. In 2026, Server-Sent Events and ReadableStream do it natively.
Progress Bar with AJAX and PHP: From Polling to Streams
In 2015, I asked a question on Stack Overflow in Portuguese about how to show a real-time progress bar during a long-running PHP process. The question scored 16 upvotes — a sign that “how do I show the user what’s happening on the server?” was a universal challenge.
The 2015 Approach: AJAX Polling
The standard solution involved three moving parts:
- Start the process — an AJAX call triggers a long-running PHP script
- Store progress — the PHP script writes its progress to a session variable or temp file
- Poll for updates — a second AJAX call runs on a
setInterval, reading the progress value
// 2015: Start the process
$.post('/process.php', { data: formData });
// Poll every 500ms
var pollInterval = setInterval(function () {
$.get('/progress.php', function (data) {
$('#progress-bar').css('width', data.percent + '%');
if (data.percent >= 100) {
clearInterval(pollInterval);
}
});
}, 500);
// progress.php
session_start();
echo json_encode(['percent' => $_SESSION['progress'] ?? 0]);
It worked, but it was wasteful. Every 500ms, the browser sent a full HTTP request just to ask “are we there yet?” — even when nothing had changed.
The 2026 Approach: Server-Sent Events
Server-Sent Events (SSE) let the server push updates to the browser over a single, long-lived HTTP connection:
// 2026: One connection, server pushes updates
const source = new EventSource('/process-stream.php');
source.onmessage = (event) => {
const data = JSON.parse(event.data);
progressBar.style.width = `${data.percent}%`;
if (data.percent >= 100) {
source.close();
}
};
// process-stream.php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
for ($i = 0; $i <= 100; $i += 10) {
echo "data: " . json_encode(['percent' => $i]) . "\n\n";
ob_flush();
flush();
sleep(1); // Simulate work
}
No polling. No wasted requests. The server sends data when it has something to report.
fetch() with ReadableStream
For more control, you can stream responses with the Fetch API:
const response = await fetch('/process-stream', { method: 'POST', body: formData });
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
const data = JSON.parse(text);
progressBar.style.width = `${data.percent}%`;
}
This approach works with any backend — Node.js, Go, Python — not just PHP. And it handles binary data too, not just text events.
WebSocket for Bidirectional
If you need the client to send messages during the process (pause, cancel, adjust parameters), WebSocket is the right tool:
const ws = new WebSocket('wss://api.example.com/process');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
progressBar.style.width = `${data.percent}%`;
};
// Cancel the process mid-flight
cancelButton.onclick = () => ws.send(JSON.stringify({ action: 'cancel' }));
Quick Comparison
| Approach | Direction | Overhead | Complexity | Best for |
|---|---|---|---|---|
| AJAX polling | Client → Server | High (repeated requests) | Low | Legacy systems |
| SSE | Server → Client | Low (single connection) | Low | Progress bars, notifications |
| ReadableStream | Server → Client | Low | Medium | Streaming responses |
| WebSocket | Bidirectional | Low | Higher | Interactive processes |
Key Takeaway
In 2015, showing server progress meant polling — asking the server every few hundred milliseconds if anything changed. In 2026, the server tells you when something changes. SSE is the simplest upgrade path for progress bars, and it works with minimal backend changes. The polling pattern isn’t wrong, but it’s solving a problem that the platform now handles natively.
Related Posts
Progress Bar with AJAX and PHP: From Polling to Streams
My 2015 Stack Overflow question about real-time progress bars used AJAX polling. In 2026, Server-Sent Events and ReadableStream do it natively.
Add to Favorites: From Browser APIs to PWA Install
My 2015 SO answer showed window.external.AddFavorite() for IE. In 2026, browsers removed programmatic bookmarking entirely — but PWA Install replaced it.
Block Special Characters in Input: From Regex to Native Validation
My 2015 Stack Overflow answer used keypress events to filter characters. In 2026, the input event and native validation do it better.