Ajax message queuing

Tuesday, June 05, 2007   —   Vienna 1 Comments

When building Wimbomedia, I encountered the problem of managing multiple HTTPRequests. In order that a new HTTPRequest does not interrupt or kill the previous one, I needed some sort of queuing abstraction that will wait until the previous request was completed (or aborted) before moving on to the next one. FIFO is a computer acronym for a concept called "First In First Out". It is a common principle in managing messages on operating systems because, lest we forget, even offline platforms have to deal with lag!

Imagine a hungry customer arrives at a counter in McDonalds to find a family of five in the throes of indecision. The person serving at the counter would wait until the family has received their order or decided they fancy Berger King instead, before asking the hungery guy to place his order. Well that's FIFO right ther — "First come, first served"! And that's how I'll propose to solve the multiple Ajax request problem.

We start by creating a namespace in which to put our functions, objects, methods, and variables. If you are already scratching your head at this point, then read this post on what namespaces are.

Let's call the namespace for this Ajax engine "Ajax". To do this we'll create an object "Ajax". In this object we will add a number of variables:

  • state – a property we will use to determine what to do with an ajax request when the engine receives it.
  • item – another object within the Ajax object. This object contains the information about our ajax request. I will go into more detail about it later.
  • queue – an array to store all the item objects.
  • loadingMsg – the default message to display as a place holder while we wait for the engine to fetch our Ajax output for this request.

var Ajax = {
	state : "idle",
	item : {url:null, canvas:null},
	queue : new Array(),
	loadingMsg : "Loading..."
};

Next we have the function "call" which receives the Ajax request, and decides what to do with it. Again, notice the function is withint our namespace "Ajax". The function takes 3 parameters:

  • script – a string of the url you which to request. eg. "submitvote.php?user=Tom&vote=4"
  • canvas – the id of the element in which to print the response from the HTTPRequest

Ajax.call = function(script, canvas)
{
	// try to show loading message
	try
	{
		document.getElementById(canvas).innerHTML = Ajax.loadingMsg;
	}
	catch(err) { /* just catch the error and nothing else */ }

	// Check if the engine is busy with an item
	if(Ajax.state == "busy")
	{
		// Busy so add this request to queue and wait/return...
		Ajax.queue.push({url:url, canvas:canvas});
		return;
	}
	else
	{
		// We're "idle" so lets process this request
		// assign the current Ajax.item to be this request
		Ajax.item = {url:url, canvas:canvas};
	}

	// Create the HttpRequest object
	if(window.XMLHttpRequest)
	{
		Ajax.xmlHttp = new XMLHttpRequest();
	}
	else if(window.ActiveXObject)
	{
		Ajax.xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
	}

	if(Ajax.xmlHttp != null)
	{
		// Append timestamp on url string to make it unique
		// otherwise it might fetch the script from the cache.
		var date = new Date();
		var stamp = date.getTime();
		var callURL = (Ajax.item.url. indexOf("?") != -1) ? Ajax.item.url + "&rand=" + stamp : Ajax.item.url + "?rand=" + stamp;

		var xmlHttp = Ajax.xmlHttp;
		xmlHttp.onreadystatechange = Ajax.callback;
		xmlHttp.open("GET", callURL, true);
		xmlHttp.send(null);

		// Set the engine state to "busy" i.e. "DO NOT DISTURB"
		Ajax.state = "busy";
	}
}

The callback function does the job of processing and printing the HTTPRequest once it's sent. once the callback has successfully printed the HTTPRequest response text or has failed and aborted, it calls Ajax.serve to bring up the next Ajax call.

Ajax.callback = function()
{
	var xmlHttp = Ajax.xmlHttp;

	if(xmlHttp.readyState == 4)
	{
		// we need to use try / catch because of Firefox
		// sometimes dies here.
		try
		{
			// try to to get a response from the HTTPRrequest
			if(xmlHttp.status == 200)
			{
				Ajax.response = xmlHttp.responseText;
			}
			else
			{
				Ajax.response = "Error";
			}

		}
		catch(e)
		{
			// if Firefox experiences a fatal error
			// catch it and move one
			Ajax.serve();
		}

		var canvas = document.getElementById(Ajax.item.canvas);

		// Try to print the response text.
		// Again, if an error occurs while trying to print
		// the resonse, we abort to the next item on the queue
		try
		{
			canvas.innerHTML = Ajax.response;
		}
		catch(e)
		{
			Ajax.serve();
		}

		// All's well so let's serve up the next customer
		Ajax.serve();
	}
}


Ajax.serve = function()
{
	// reset state
	Ajax.state = "idle";

	if(Ajax.queue .length > 0)
	{
		// call up the next customer on the queue
		var A = Ajax.queue[0];
		Ajax.call(A.url, A.canvas);

		// take this customer off the waiting list
		Ajax.queue.splice(0,1);
	}
}

And there we have it. So a call to the engine will look something like Ajax.call("showComments.php?post=316", "comment_div"). Off course you can add more features like prioritizing certain types of requests or having more than one type of callback .

If you've found this useful let me know. I'd also be happy to clarify or re-explain anything.

Comments  (1 so far)

Petro Jun 14 08

Standby for an updated version of this approach. I'll post a tutorial based on the work I've been doing on the Wimbomedia Sandbox

Add a comment

The fields that are highlighted contain errors
Name:
Email:     (Not shown)
Website:
Comment:
  Notify me when my comment has been cleared.
  Notify me of followup comments.