How to update the browser on the status of an AJAX process




Websites with AJAX elements are a defacto standard on the web and have been for quite some time. AJAX allows you to run a request on the server without actually leaving the page the user is on, executing it asynchronously while the browser waits for a response.

One big problem with this is that if the process takes more than a few seconds, there’s no built in methods in the AJAX standards to receive content before the entire request is complete, so how do we let the browser know the status of the process on the server, if we can’t stream information to the browser using AJAX? We’ll be addressing this problem in this post.

There are several options for updating the client on the status of a long running process, some better than others:

  • Web Sockets
  • iFrame Streaming
  • AJAX Polling
  • And more…

These are all fairly valid solutions, but there’s only one that has truly universal support and offers a really robust solution. Let’s go over the advantages and disadvantages of each.

Web Sockets

This solution seems like it would solve all of our problems. Web sockets allow bi-directional communication between the server and the browser, keeping a pipeline between the two open for communication. Fantastic! Problem solved! We can just send packets along to update the client whenever we want, but, there’s a big problem – on shared hosting environments, this can be very hard, if not impossible, to accomplish.

Not only are web sockets hard to realise on shared servers, but they consume a lot of memory and processing power as they tend to run constantly in the background.

For simple projects, then, web sockets aren’t a real solution.

Advantages:

  • Web sockets were almost made for this process.
  • Offers complete bidirectional talking between browser and server.

Disadvantages:

iFrame Streaming

This involves a technique where you embed an iFrame of a page that contains an endless loop that outputs <script> elements to update an element on the page constantly, something like the below:

<?php 
set_time_limit(0); 
header("Cache-Control: no-cache, must-revalidate"); 
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); 
flush(); 
?> 
<html>
<body>
<span id="output">100 to go</span>
</body>
</html>
<?php
$i = 100;
do {
// some long process
echo "<script type='text/javascript'>document.getElementById('output').innerText = '{$i} to go'; </script>";
flush(); 
} while($i>0);

While this works, sometimes, it’s not very  reliable, due to idiosyncrasies with PHP’s flush() function, which will only output to the browser every N bytes (usually 1024 bytes by default, but may be more or less depending on your server settings). This is something that can be changed, but not easily, and certainly not easily on shared hosting environments.

This method also falls down when you need to update the browser very often, or many, many times, for example if the PHP loop had 100,000 elements instead of 100. The DOM would be overloaded and probably end up crashing the browser.

iFrames are also inherently incredibly slow and clunky and offer little in terms of styling or control. As such I would say this solution is not recommended.

Advantages:

  • Incredibly simple
  • Good all round support

Disadvantages:

  • iFrames are clunky, ugly and slow
  • Not good for longer processes

AJAX Polling

Finally, we land at a fairly well rounded solution that can be implemented in a number of ways. The long and the short of it is this:

  1. User initiates long running AJAX process.
  2. Script is stuck waiting for the script to process before any information is sent back to the browser.
  3. Meanwhile, long running AJAX process updates a file on the server somewhere with it’s current progress.
  4. A separate AJAX request is called to get the contents of that file and display it to the user every N seconds.

It’s a pretty simple idea, with the benefit of almost universal support – it only relies on the technology you’re using anyway – AJAX.

To implement this, you’ll need 3 parts to your application:

  1. The long running process, which we’ll put in longRunningProcess.php
  2. The progress file, which we’ll put in progress.json
  3. The client facing file, which we’ll put in ajaxProcess.html

The contents of your long running process will look something like this:

<?php
set_time_limit(0);
$totalItems = 10;
for($i = 0; $i <= $totalItems; $i++){
// some long running code
// sleep 1 second
usleep(1000*1000);
// write our output file
file_put_contents(
'progress.json', 
json_encode(array('percentComplete'=>$i/$totalItems))
);
}
echo json_encode(array('message'=>"All done";
exit(0);

Pretty simple! Now our progress.json file just has the percentage complete of the total process.

Now, our ajaxProcess.html file can be equally simple:

<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
<body>
<span id="output"></span>
<!-- jQuery...makes AJAX easier! -->
<script src="//code.jquery.com/jquery.js"></script>
<script>
window.pollingPeriod = 100;
window.progressInterval;
$.getJSON('longProcess.php', function(data){
clearInterval(window.progressInterval);
$('#output').html('Woohoo, all done! Message from server: ' + data.message);
}).error(function(data){
clearInterval(window.progressInterval);
$('#output').html('Uh oh, something went wrong');
});
window.progressInterval = setInterval(updateProgress, window.pollingPeriod);
function updateProgress(){
$.getJSON('progress.json',function(data){
$('#output').html(data.percentComplete*100 + ' complete');
}).error(function(data){
clearInterval(window.progressInterval);
$('#output').html('Uh oh, something went wrong');
});
}
</script>
</body>
</html>

And there you have it, very simple!

Advantages:

  • Incredibly simple
  • Almost universal browser support (less than 0.001% of browsers won’t support it)
  • Flexible, scales very well
  • Handles multi-stage processes well
  • Flexible to upgrading

Disadvantages:

  • Care needs to be taken that separate processes don’t overwrite the same file
  • How often your progress updates relies directly on how often you poll, and polling can be expensive. This can be rectified by sending a ‘rate‘ of processing along with the status and polling less often, but updating the display more often.

There are endless possibilities with this method.

Example

Here’s a more elaborate example using multi-stage processes with a fancy progress bar. (View this example in a new tab/window)

Here I’ve used several different stats pulled from the progress file to update the display, especially the rate, as it only actually polls every 1.5 seconds but updates every 250 milliseconds.

A Library

I’ve written a PHP library to help do all this stuff for you, you can check it out on GitHub and suggest improvements or bugfixes.

GitHub: AjaxProcessUpdater

Enhancements

  1. Try passing in the filename to your longProcess.php file and reading that file, including some random number so that cross processes don’t affect eachother.
  2. Try improving the rate predictions so that you can poll less.
  3. Try storing the data in a session variable instead of a json file.
  4. Try being able to stop a process by calling another file, stopProcess.php, that creates a .stop file that the main loop looks out for each iteration and stops if it’s present.

Leave a Reply