A lot of code that I have seen over the years always assumes success, particularly with AJAX calls. This creates code that is fragile, and entirely dependant on the result of external (to the client) code.
There are a few ways we can attempt to protect against this. The first, is to always depend on the result of the call to make any changes to the DOM or UI. For example, let’s say we have a quantity field in a shopping basket on an e-commerce site. When the user clicks a plus next to the field, we make an AJAX call that increments a value in a basket stored server-side and updates the UI to match. It is very common, and tempting, to increment the value in the UI immediately, and then make the AJAX call. There are a few obvious problems here. If the AJAX call fails, now we have a UI that is inaccurate. We can decrement the value in the UI to make up for it, but now we are starting to confuse the user with the numbers jumping up and down, and if they have clicked the button six times in quick succession (very common use of this kind of control) our chance of error for accurate UI representation of data increases dramatically.
A much safer way to do this is to return the current stored quantity value from the server with each AJAX call, and only update the UI when the AJAX call completes. This results in a less “snappy” feeling UI, and it will be necessary to display some kind of visual cue that something is going on the background, but the user learns quickly how the site works and this is a much more robust process.
Without having any kind of genuine statistics to call upon, I would suggest that nine out of ten AJAX calls that fail are due to an issue that is temporary and would be resolved by a retry. Anything due to network issues, a lost packet somewhere, a brief server glitch, load balancing problems and so on can cause a timeout or 404 without the target resource actually being missing or consistently failing. Often when a website fails to load, I click refresh and there it is. An AJAX call is just the same. (The same applies to database connections, or anything dependant on a network resource.)
Therefore, rather than display an error on an AJAX timeout (“we could not connect to the server” or “there was a problem, please try later”) or worse, doing nothing at all, there are some things we can try to resolve the issue ourselves without bothering the user about it until we’re certain that it is broken.
Let’s look at a typical jQuery AJAX call.
$.ajax({ url : 'ajaxurl.json', type : 'get', data : {name : 'value'}, dataType : 'json', timeout : 20000, success : function(json) { //do something } });
The immediate problem with this is it completely assumes (and depends upon) success. There is not even a basic error handler. Something like this is better:
$.ajax({ url : 'ajaxurl.json', type : 'get', data : {name : 'value'}, dataType : 'json', timeout : 20000, success : function(json) { //do something }, error : function() { alert('Oops! There was a problem, sorry.'); } });
The error callback function is actually passed three arguments.
- The XMLHTTPRequest object in use
- A textual equivalent of the status
- The actual exception thrown
These allow us to react in a more sophisticated manner:
$.ajax({ url : 'ajaxurl.json', type : 'get', data : {name : 'value'}, dataType : 'json', timeout : 20000, success : function(json) { //do something }, error : function(xhr, textStatus, errorThrown ) { if (xhr.status == 500) { alert('Oops! There seems to be a server problem, please try again later.'); } else { alert('Oops! There was a problem, sorry.'); } } });
Since the error function lives inside the AJAX object itself, and is called in that context, the this
keyword very usefully points to the jQuery AJAX instance itself. Using this, and the arguments we are being passed we can very easily set the UI to retry the AJAX call on our behalf. Let’s attach some extra properties to the AJAX object:
$.ajax({ url : 'ajaxurl.json', type : 'get', data : {name : 'value'}, dataType : 'json', timeout : 20000, tryCount : 0, retryLimit : 3, success : function(json) { //do something }, error : function(xhr, textStatus, errorThrown ) { if (xhr.status == 500) { alert('Oops! There seems to be a server problem, please try again later.'); } else { alert('Oops! There was a problem, sorry.'); } } });
We have added tryCount
and retryLimit
. These are going to store how many attempts we have made, and how many attempts we will make respectively. Making use of these:
$.ajax({ url : 'ajaxurl.json', type : 'get', data : {name : 'value'}, dataType : 'json', timeout : 20000, tryCount : 0, retryLimit : 3, success : function(json) { //do something }, error : function(xhr, textStatus, errorThrown ) { if (textStatus == 'timeout') { this.tryCount++; if (this.tryCount <= this.retryLimit) { //try again $.ajax(this); return; } alert('We have tried ' + this.retryLimit + ' times and it is still not working. We give in. Sorry.'); return; } if (xhr.status == 500) { alert('Oops! There seems to be a server problem, please try again later.'); } else { alert('Oops! There was a problem, sorry.'); } } });
So, now we are trying three times before giving in. Where I have actually used this, some modal dialogue boxes are used instead of window.alert() and I save a copy of the AJAX object when we reach our retry limit. At that point, although I tell the user we have given in, I still provide them with a button to try again themselves.
I am convinced that implementing techniques like this will rid us of many unnecessary bad user experiences, and many support calls. Keep the user informed (in simple language!), and assume things will fail. I hope this is helpful.