=^.^=

Reliable AJAX: Timeout and Retry XMLHttpRequest

karma

A lot of AJAX seems to rely on ideal conditions: a server that is both running and in good health, a reliable high speed internet connection, a lack of packet loss etc. Unfortunately, this is not always the case - what happens when your user triggers an event and the daemon is restarting or they are hopping access points/cell towers? What happens if the server load is sky high and only half of the requests are getting through? AJAX isn't a very robust technology but we can increase the odds of our XMLHttpRequest transactions succeeding with some rudimentary JavaScript.

Newer Msxml XMLHTTP implementations support runtime-definable connection time-outs but that covers at best half of your audience and is therefore irrelevant to our needs. We will be implementing our own cross-browser compatible time-outs with the setTimeout() function. Once the time-out expires we will kill the XHR and repeat the process again, with a slightly longer time-out. The process is repeated as many times as necessary; each time increasing the duration. This is called backoff - similar in concept to but much less complicated than the exponential backoff algorithm Ethernet uses to recover from packet collisions.

The tricky part is to set the initial time-out to a value which gives the browser as long as reasonably possible to send and receive the request under strained conditions. Depending on how long the round trip takes in ideal circumstances a safe value can range anywhere from (plus) less than one second to several seconds - this is going to be unique to your application, resources and network so test thoroughly. If the time-out is too low we will be doing more harm than good by stopping and re-sending the request before it has even had a chance to complete. It might be sensible for you to give the last attempt a huge time-out.

The other major factor to consider is whether "the user knows best" in the given situation or not. If you are giving a visual cue to the user that there is AJAX activity happening in the background or if they expect to see something on the page update once the request has completed they might notice that things are taking a little long to react and re-trigger the event (read: hammer on the button like a mindless idiot). If you don't want them to interfere with the time-out and re-send procedure take that into account when setting up your trigger - the example we are going to use assumes the user knows best. If they hammer on the button the best case scenario is their request goes through faster than if they left it alone and the worst case scenario is once they stop hammering the time-out and re-load cycle will run its course from the top anyway.

// Initialize global variables
var attempts = 0;
var timeout = 1000;    // In miliseconds
var max_attempts = 5;  // Six from 0
var script_url = ''    // The location of the target processing script
var junk = '';         // The variables we're POSTing, probably set in trigger()
var t = '';

// Cross-Browser XMLHttpRequest()
function createXHR()
{
	try { return new XMLHttpRequest(); } catch(e) {}
	try { return new ActiveXObject("Msxml2.XMLHTTP.7.0"); } catch (e) {}
	try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
	try { return new ActiveXObject("Msxml2.XMLHTTP.5.0"); } catch (e) {}
	try { return new ActiveXObject("Msxml2.XMLHTTP.4.0"); } catch (e) {}
	try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) {}
	try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
	try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
	alert("Please enable ActiveX or update your browser.");
	return null;
}

// User calls this function, i.e. onclick event handler
function trigger()
{	
	// Re-set the time-out in case the user interrupts the cycle
	clearTimeout(t);
	attempts = 0;
	timeout = 1000;

	driver();
}

// Handle the state changes of the XHR
function callback()
{
	if (xmlhttp.readyState==4 && xmlhttp.status==200)
	{
		// Success! Re-set the time-out for the next round.
		clearTimeout(t);
		attempts = 0;
		timeout = 1000;

		xmlhttp = '';

		// Perform whatever action we need to do on success.
	}
	else
	{
		// Error handling/Request status indication
	}
}

// The guts of the request
function driver()
{
	xmlhttp = createXHR();

	xmlhttp.open("POST",script_url,true);

	t = setTimeout(function(){timedout();}, timeout);
	xmlhttp.onreadystatechange=function(){callback();}

	xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
	xmlhttp.send(junk);
}

// Run this if we time out
function timedout()
{
	if(typeof(xmlhttp) == 'object')
		xmlhttp.abort();

	if(attempts < max_attempts)
	{
		// We timed out. Back off the time-out a bit.
		attempts++;
		timeout = timeout + 200;
		// We're adding 200ms to the timeout each iteration, YMMV.
		// Maybe if attempts == max_attempts make the timeout really huge.

		// Play it again, Sam!
		driver();
	}
	else
	{
		// We failed. Set the timeout back to default for the next round.
		timeout = 1000;
		attempts = 0;
		xmlhttp = '';
		alert("Timed out while sending request to server.");
	}
}

Comments

• Louis St-Amour

Want to make the timeout fancy, like that exponential backoff?

Just replace timeout = timeout + 200; with timeout = timeout*2;

Done! :)

martin Webb

I think the thread here which discusses why the article author is not using a framework like jQuery is an interesting one.

One must consider that in app development their are a number of different requirements and demands. For sure, if your building a script, a site or something that is not inventing a wheel your client requires you to deliver the wheel, not re-invent it. And thus of course he is not going to spend a fortune of time waiting for you to do it, or dollars for that fact. This kind of development work is done best with lets face it jQuery.

On the other hand if your building out the latest in mobile real-time cloud hosted apps that will be used by thousands of users. Uses cutting edge development such as model driven dom views, offline index caching, real-time client to client and client to server push updates whilst you can prototype this to some end in a a framework. Something that is really going to push the boundaries might need simplified, streamlined code patterns that mean the user gets a tailored experience that can't be found elsewhere at this moment in time.

Of course once you invent the wheel, it will be re-invented many times in boilerplate frameworks.
But the point is - someone somewhere has to do it from the ground up before someone can simply, remodel and roll-out.

Both critics have great arguments. But i think when someone takes the time to explain how to do something from the ground up, one should give kudos and remember someone somewhere is building the next framework that will allow you to make money with simple one line code calls. These kind of articles help those types of coders learn the already invented secrets of those that walked before them.

Respect to all that work with code!

Ausin

I too use setTimeout() to implement timeouts in XHR because support to the timeout property is limited (firefox 12 supports it).

My mobile application uses XHR and as it happens, and XML protocol to communicate with a server. It is designed to be used over 3g and may not always be able to contact the server either due to coverage or network issues.

In fact I did a bunch of testing over E (older than 3G) and it was a very unreliable connection. This allowed me to both perfect the protocol (for instance, how does the server know a client really received the data that was sent) and the XHR timeouts.

Simply setting a timeout large enough for a reasonably slow connection sending back a document of unknown size did not give the experience I wanted. The timeout would need to be so large to cater for the worst case scenario, that the client would wait around for ages before deciding there was a problem.

The problem with your backoff method in my scenario is that you could be aborting perfectly happy but slow/large response unnecessarily.

In the end I opted for a progressive timeout. The theory is, to time out quickly when there is a problem, but to keep going while there isn't.

The timeout needs to be kept just into the future.

I first implemented this using dojo.xhr as I was writing my app in dojo. This means I had to keep extending the dojo xhr timeout into the very near future. I did this by recording the start time, and in onreadystatechange, I calculate the elapsed time, and I then set the timeout to elapsed + N seconds (where N is the specified progressive timeout).

I then wrote the xhr-progressive-timeout API as a standalone XHR solution. It uses setTimeout(handler,N) which is just replaces continually during onreadystatechange events.

In both cases, while the various ready states tick over (0, 1, 2, 3) the timeout is constantly updated (by default to 20 seconds from now) so the connection will keep going and going and going for as long as is required, provided data keeps flowing.

As soon as there is a network stall, that lasts longer than the specified (short) timeout, the connection is aborted and a timeout handler called.

karma

I love you litey :3

Like I said, if it saves you assloads of time it's kosher in my view. But as you know, in my current situation it's the speed of execution I'm being paid for, not the deadlines. Time is money and nanoseconds are dollars, baby!

• litestar

Meh. I end up writing too much JS to worry about making sure the nuances of JS for every site I write. I'm more concerned about the speed of implementation than about the speed of execution; I need to be able to pump out work & get shit done NAO rather than fiddle with everything & anything. In the end, I'd just create something akin to a minimal jQuery/mooTools/Dojo/whatever that did the exact same thing to save time. It's Greenspun's tenth rule applied to JS code: any sufficiently complicated JS code base includes an ad-hoc, informally-specified, bug-ridden, slow implementation of jQuery. Different development mindsets.

• litestar

Out of curiosity, why are you rolling your own & not relying on some toolkit ala jQuery or the like?

karma

Three reasons:

- jQuery is overkill for a lot of situations, particularly if all you're doing are a few AJAX transactions and the speed of those transactions is of the utmost importance. It's good practice to ask yourself why you're including this multi-kilobyte library with any number of features you're not going to put to use to do something simple and easily accomplished without.
- AFAIK jQuery only provides a built-in facility for timeouts; to implement retries you would still have to write much of this structure.
- I want to be a good programmer and being a good programmer means learning how to do minutia on your own. It's the difference between being able to write toolkits and needing them to get by. Why invest my time in learning what may as well be a whole different language when I can learn actual JavaScript that is useful in any situation?

I don't think it's a reasonable default to throw your hands up in the air and say "let's let jQuery sort out the differences in the browsers" or supposed inadequacies in JavaScript itself every time you approach a problem. In my humble opinion if you can't cope with cross-browser nuance you don't really know JavaScript (or HTML or CSS for that matter) even if it's the browser's fault for not adhering to the standard. I feel that jQuery - like any third party library - is only appropriate where it may spare you hours and hours of coding to accomplish a low-return task like frilly user-interface elements.

In my mind, jQuery is like BASIC and pure JavaScript is like C - BASIC lets me make applications quickly and easily with broad-sweeping, powerful commands but the cost of that is sacrificing low-level control. In C, it takes a lot more code to go the same distance but more is exposed and the greater flexibility means being able to efficiently accomplish my goal without adhering to the confines and conventions of strong but inaccessible BASIC commands.

Sometimes it makes sense to use jQuery and I happen to use it quite a lot. However, if the first thing you do when starting a project is include it without thinking critically about why you need it you're not really a JavaScript programmer, you're a jQuery user.