AJAX onreadystatechange handler parameters?

Here’s what I’m doing:

As part of a big project, I’m trying to tie in multiple status-monitoring applications into a single “dashboard” page that will display a good representation of a summary of each monitoring tool. Currently this page has 3 tools tied in, but this will grow into a much larger number rather quickly (3 more within a week of finishing tying in these 3, for example).

Here’s how I’m doing it:

Basic screen scraping - I issue an HTTP request from the server to the tool, grab the piece I want, then display it. You can see that even with only 3 tools currently this takes some time, and this will only grow longer with more tools. The obvious solution to reducing or at least hiding this delay is to scrape asynchronously - this is where my beloved AJAX comes in.

Here’s my problem:

To reduce the impact of a single slow tool bogging down the entire application, I’m trying to issue an AJAX request for each tool. The problem I’m having is that I can’t figure out how to tell each object which tool it represents! After Googling for quite some time, I came across what I thought would work:


http.onreadystatechange = function(args){if(http.readyState==4){alert(args)}};

Sadly, that doesn’t work - args is undefined.

So, my question is thus: is it possible to do this without writing separate onreadystatechange handlers for each request? My initial idea was to simply give each tool its own onreadystatechange handler, but that will real quickly grow out of control! Any one have any suggestions?

Hi kromey,

It’s hard to say, without seeing more of your code - especially the function which contains the code you posted. But, yes, it is possible.

From your post I assume you use only one XHR object and a new request is not started until the previous one has finished - is that right? In that case, yes you can use the same onreadystatechange handler.

Hi Mike,

Thanks for the response. I’ll post my sad attempts at solving this problem when I’m at the computer where my code is. In the meantime, to answer your question, no - I’m using multiple XHR objects and running all the requests simultaneously. At least, that’s what I want to do (should reduce load time from the user’s perspective, and this is an application where fast load times are critical).

The JavaScript functions (defined in the HTML head section):


function createRequestObject() {
	var ro;
	var browser = navigator.appName;
	if(browser == "Microsoft Internet Explorer"){
		//IE implements the XML request object as an ActiveX object
		ro = new ActiveXObject("Microsoft.XMLHTTP");
	}else{
		//Other browsers use a JavaScript XMLHttpRequest object
		ro = new XMLHttpRequest();
	}
	return ro;
}

function sndReq(action,args) {
	var http = createRequestObject();
	http.open('get', action+'?'+Math.random(), true);
	http.onreadystatechange = function(args){if(http.readyState==4){alert(args)}};
	http.send(null);
}

And then the calls (right now each is calling the same simple “Hello World”-esque test page), currently sloppily tossed into the body tag’s onload event (cleaned up here for ease of reading):


sndReq('test.html','1');
sndReq('test.html','2');
sndReq('test.html','3');
sndReq('test.html','4');
sndReq('test.html','5');

Right now I get a series of 5 alert boxes that each say “undefined”, whereas I’m expecting to see the numbers 1 - 5 in each one (though not necessarily in order - I’m not that naive!).
The idea is to eventually use the second ‘args’ parameter to define where on the page the result should be displayed, e.g. via document.getElementById(args).innerHTML = XHR.responseText. However, as mentioned I can’t figure out how to pass this parameter into the handler; additionally, I don’t know how I’d reference an XMLHttpRequest object to retrieve the responseText without making it a global variable.

The only workable solution I’ve found so far is to make a bunch of global XHR objects and separate distinct code snippets to initialize and fire off each one (i.e. essentially replicate sndReq above to handle each request).

Does anyone have a solution to my problem that is cleaner than bogging down my code with a dozen different XHR objects each fired and handled separately? Essentially the core of the problem is that I have multiple requests I want to fire off simultaneously, each needs to be handled slightly differently (i.e. needs to be displayed in a different place on the screen), and I need the solution to be easily scalable in that I need to be able to easily add new targets. I do have full control over what I am requesting, so don’t hesitate to offer a solution that requires that I change the way the data is sent from the server.

http.onreadystatechange = function([COLOR="Red"]args[/COLOR])

Remove “args”. That should solve the problem.

But actually, I would recommend this way:

function myFunction1(xmlHttp) {
	alert ('myFunction1 - ' + xmlHttp.responseText);
}

function myFunction2(xmlHttp) {
	alert ('myFunction2!' + xmlHttp.responseText);
}

function myFunction3(xmlHttp) {
	alert ('myFunction3!!' + xmlHttp.responseText);
}

function sndReq(action, callback) {
	var http = createRequestObject();
	http.open('get', action+'?'+Math.random(), true);
	http.onreadystatechange = function(args){if(http.readyState==4){callback(http)}};
	http.send(null);
}

sndReq ('test.html', myFunction1); // Call myFunction1 and pass the xmlHttpRequest object to the function when the document is loaded.
sndReq ('test.html', myFunction2); // And so on...
sndReq ('test.html', myFunction3);

Wow! I can’t believe it was something so trivial! Thank you so much!!

Why would you do it this way? I can see this easily growing out of hand as I include a dozen callback functions, and since every single one is going to be doing the exact same thing in my case I don’t see the advantage to doing it this way. It is definitely something I’ll keep in mind for the future if I do have to handle different requests differently, though.

Sorry I was late getting back to this thread, but ‘the DtTvB’ is right on :tup:, thanks for jumping in!

Note that the reason it works (removing ‘args’) is because ‘args’ now references the argument to sndReq (via closure) instead of its own argument (whose value is undefined).

You won’t have to make global objects. Notice, in the code posted by ‘the DtTvB’, how a reference to the XHR object is passed to the callback function. The XHR object in that function “appears” to be a local variable - however, the reference to it from within the anonymous function creates a closure and so the XHR variable persists. (i hope my understanding of this is correct - if not, someone please correct me)

You can use that idea to write an onreadystatechange handler which will handle all XHR responses. For example:


function sndReq(action, id)
{
  var http = createRequestObject();
  http.open('get', action+'?'+Math.random(), true);
  http.onreadystatechange = function(){if(http.readyState==4){xhrHandler(http,id)}};
  http.send(null);
}
function xhrHandler(xhr, id)
{
  var e = document.getElementById(id);
  if (e) {
    e.innerHTML = xhr.responseText;
  }
}

Thanks for the excellent explanation, Mike! I’d never heard of the concept of closures before (for anyone else who’s as confused as I was, I found this page, which is quite technical but explains closures very thoroughly), and was thinking in terms of scope, i.e. that the variables would disappear when the sndReq function finished and thus not be available to the callback function.

Thanks to both of you, I now have a fully functional script that does everything I need it to, and is easily expandable as I have to add additional tools to this dashboard.

Edited for brevity:


function createRequestObject(); //as above

function entops_sndReq(action,target,delay) {
	var http = createRequestObject();
	http.open('get', action+'?'+Math.random(), true);
	http.onreadystatechange = function(){
		if(http.readyState==4) {
			if(http.status==200)
				document.getElementById(target).innerHTML = http.responseText;
			setTimeout('entops_sndReq(\\''+action+'\\',\\''+target+'\\','+delay+')',delay*1000);
		}
	};
	http.send(null);
}

function entops_init() {
	entops_sndReq('ajax/nagios.php','nagios',60); //Nagios; refresh every 60 seconds
	entops_sndReq('ajax/statseeker.php','statseeker',60); //Statseeker; refresh every 60 seconds
}

This brings up another question: Mike’s example shows an anonymous callback function whose sole purpose is to call a separate global function; my code implements the entire thing in the anonymous callback function. What are the pros/cons of each method? Should my code follow Mike’s pattern, or is it good as-is?

In this case I too would implement all of it inside the anonymous function.

Yes, that page at jibbering.com is excellent, and was also one of my first intros to closures.

Your latest code post brings up a different issue. Creating 2 XHR objects every minute means the page’s memory usage will continually grow and grow. Remember we talked about how the closure causes the local vars (and parameters) of sndReq to persist. The question is… when do they eventually go out of scope and get cleaned up by the garbage collector? Or do they every get cleaned up at all? For me, this is always a difficult question when it comes to closures.

So I ask myself what causes the closure? Well, it is the references, from within the anonymous func, to the local var and parameters of sndReq. So “I think” that if we could do this: http.onreadystatechange = null; then the closure would become available to be garbage-collected. So here’s an idea - notice my code after the “clean up” comment:


function entops_sndReq(action,target,delay) {
  var http = createRequestObject();
  http.open('get', action+'?'+Math.random(), true);
  http.onreadystatechange = function(){
    if(http.readyState==4) {
      if(http.status==200)
        document.getElementById(target).innerHTML = http.responseText;
      setTimeout('entops_sndReq(\\''+action+'\\',\\''+target+'\\','+delay+')',delay*1000);
      // clean up
      http.onreadystatechange = null;
      http = null;
    }
  };
  http.send(null);
}

I’m not 100% sure that this does what I’m intending for it to do. I hope someone will jump in here and correct me if I’m wrong, and help us to better understand this. I guess it just needs testing. Try it without and with the “clean up” and watch the memory usage. I would change from 60 secs to perhaps 1 sec - just for the test.

After quite a bit of testing and more time than is probably healthy staring at that flickering line in Task Manager, it appears that you method works better for slowing down the memory leak; without explicitly telling the garbage collector that the http object is no longer needed, memory usage grows at an alarming speed. Memory usage still grows after setting http.onreadystatechange and http to null, however its considerably slower (most likely the growth can be attributed to garbage collection not running immediately); incidentally, my tests show that “destroying” the object prior to the call to setTimeout slows the memory leak even further, although I have no idea why.

Firefox and IE7 differ, of course, although in some (perhaps) unexpected ways:
IE7 chokes on “http = null;” unless you do not first set http.onreadystatechange to null (i.e. you must delete the line or the script halts at that point).
Firefox’s garbage collection seems to run every 5-10 seconds, reducing memory usage at that time; IE7 doesn’t seem to have a garbage collection routine at all, at least not one that is able to catch the memory leak caused by spawning so many XHR objects.
On page refresh, Firefox returns to pre-XHR objects memory usage*; IE7 only drops a few hundred KB, regardless of how many megs of RAM the XHR objects have seized. (*Firefox isn’t quite perfect on this one, growing by anywhere from 50-100KB from refresh to refresh.)

Since this is an application that is meant to sit open for hours or even days at a time, it looks like I have no choice but to use global XHR objects, unless someone else has a brilliant idea that can solve this memory leak (it’s not atrocious, but as more tools are added (necessitating more XHR objects) the leak will increase exponentially, and this will be open long enough that memory usage could very well become a problem).

Added some basic memoization to the createRequestObject function, and it seems to have very nearly solved the memory leak problem. IE7 shows absolutely no increase in memory usage, while Firefox will creep up slowly and then drop down suddenly (presumably when garbage collection runs).

Here’s the updated code:


var ros = new Array;

function createRequestObject(index) {
	if (ros[index]) return ros[index];
	var ro;
	var browser = navigator.appName;
	if(browser == "Microsoft Internet Explorer"){
		//IE implements the XML request object as an ActiveX object
		ro = new ActiveXObject("Microsoft.XMLHTTP");
	}else{
		//Other browsers use a JavaScript XMLHttpRequest object
		ro = new XMLHttpRequest();
	}
	ros[index]=ro;
	return ro;
}

function entops_sndReq(action,target,delay) {
	var http = createRequestObject(target);
	http.open('get', action+'?'+Math.random(), true);
	http.onreadystatechange = function(){
		if(http.readyState==4) {
			if(http.status==200)
				document.getElementById(target).innerHTML = http.responseText+'<br /><small>Data refreshes every '+delay+' secs</small>';
			setTimeout('entops_sndReq(\\''+action+'\\',\\''+target+'\\','+delay+')',delay*1000);
		}
	};
	http.send(null);
}

function entops_init() {
	entops_sndReq('ajax/nagios.php','nagios',60); //Nagios; refresh every 60 seconds
	entops_sndReq('ajax/statseeker.php','statseeker',60); //Statseeker; refresh every 60 seconds
}

Thanks everyone for your help, this seems to do exactly what I needed it to! :slight_smile: