Weird Thoughts From Eric's Head

Categories : All | AJAX | BUSINESS | PERSONAL | PROGRAMMING | BOOK REVIEW

The XMLHttpRequest Reuse Dilemma

There is a better solution that this posted here: http://radio.javaranch.com/pascarello/2006/03/31/1143817890773.html. So you can read this just to see the wrong way of doing it and the right way is on that link!

One thing I see popping up over message boards is people want to reuse the XMLHttpRequest Object instead of creating a new instance every single time. Some developers think this will help in memory leaks. I have not tested it in any way to see if it really helps. But I thought that I would show you it is possible to reuse the same object over again and again with Internet Explorer.

For this code I am going to be using a very basic example. I am not going to use OO code so just bear with it. With Firefox you are able to reuse the XMLHttpRequest object without this craziness, but with Internet Explorer 6 you are not able to do that (I have not tested this in IE 7 yet so hopefully someone will comment on the reuse factor!)

The basic reuse example

So with the following two buttons below, I will try to load different xml documents. In Firefox you should have no issues loading each one as long as you wait for the file to load completely before pushing the second button. (You need to Remember I am not doing OO stuff here, hence why you need to wait until you push!) With IE6 you should be able to only load one of the documents and the other one should not load. So click away!

Now the code for this request is below so you can see what it is doing exactly.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Example 1 - Basic Request</title>
    <script type="text/javascript">

      function getXHR(){
        var newReq = null;
		if(window.XMLHttpRequest) {
          try {
            newReq = new XMLHttpRequest();
          }
          catch(e) {
            newReq = false;
          }
        }
        else if(window.ActiveXObject) {
          try {
            newReq = new ActiveXObject("Msxml2.XMLHTTP");
          }
          catch(e) {
            try {
               newReq = new ActiveXObject("Microsoft.XMLHTTP");
            }
            catch(e) {
              newReq = false;
            }
          }
        }
	return newReq;
      }

      var req_fail_IE = getXHR();
      function loadXHR_fail_IE(url) {

        if(req_fail_IE) {
          req_fail_IE.onreadystatechange = processReqChange_fail_IE;
          req_fail_IE.open("GET", url, true);
          req_fail_IE.send("");
        }
        else{
          alert("The XMLHttpRequest Object is not supported");
        }
      }

      function processReqChange_fail_IE() {
        if (req_fail_IE.readyState == 4) {
          if (req_fail_IE.status == 200 || req_fail_IE.status == 0) {
            alert(req_fail_IE.responseText);
          }
          else {
            alert("There was an issue retrieving the data:\n" +
                  "Reason: " + req_fail_IE.statusText);
          }
        }
      }

    </script>
</head>
<body>
  <form id="Form1">
    <input type="button" name="b1" value="Bad - Test1" onclick="loadXHR_fail_IE('Test1.xml')" />
    <input type="button" name="b2" value="Bad - Test2" onclick="loadXHR_fail_IE('Test2.xml')" />
  </form>
</body>
</html>

As you can see the example above shows the problems with cross browser differences. So we need to find a solution that solves it. Luckily it is rather easy to get the cross browser solution working.

The abort reuse example

How do we get around this problem with IE? Firefox is smart ought to figure it out! The solution is actually really simple. It involves the abort() method. Seems like everyone forgets the abort method exists. The abort method cancels the request. So if we call the abort() method on our XMLHttpRequest object, we are able to reuse it again with IE. Click the following buttons with IE and you will see that both files load (Remember no OO here so wait till it loads to execute the second click!)

You can see all we needed to do was add a little if else statement in the function on the code listing below.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Example 1 - Basic Request</title>
    <script type="text/javascript">
      var req = null;

      function getXHR(){
        var newReq = null;
        if(window.XMLHttpRequest) {
          try {
            newReq = new XMLHttpRequest();
          }
          catch(e) {
            newReq = false;
          }
        }
        else if(window.ActiveXObject) {
          try {
            newReq = new ActiveXObject("Msxml2.XMLHTTP");
          }
          catch(e) {
            try {
               newReq = new ActiveXObject("Microsoft.XMLHTTP");
            }
            catch(e) {
              newReq = false;
            }
          }
        }
	return newReq;
      }


      function loadXHR(url) {
        if(req==null){
	  req = getXHR();
	}
	else if(req){
          req.abort();
	}
        if(req) {
          req.onreadystatechange = processReqChange;
          req.open("GET", url, true);
          req.send("");
        }
        else{
          alert("The XMLHttpRequest Object is not supported");
        }
      }

      function processReqChange() {
        if (req.readyState == 4) {
          if (req.status == 200 || req.status == 0) {
            alert(req.responseText);
          }
          else {
            alert("There was an issue retrieving the data:\n" +
                  "Reason: " + req.statusText);
          }
        }
      }

    </script>
</head>
<body>
  <form id="Form1">
    <input type="button" name="b3" value="Good - Test1" onclick="loadXHR('Test1.xml')" />
    <input type="button" name="b4" value="Good - Test2" onclick="loadXHR('Test2.xml')" />
  </form>
</body>
</html>

To implement this type of logic in an OO JavaScript manner would be tricky, ah not really if you coded it well. Hopefully someone out there will run tests to see if this actually helps cut down on memory leaks or actually creates a bigger one. That is a good project for someone!



Eric Pascarello
Coauthor of Ajax In Action
Moderator of HTML/JavaScript at www.JavaRanch.com
Author of: JavaScript: Your Visual Blueprint for Dynamic Web Pages


Eric, Please see my article. You can reuse XMLHttpRequest object without those abort calls. http://keelypavan.blogspot.com Thanks Pavan Keely
How does one usually go about testing memory leaks on a browser? Is it as simple as running a long test and monitoring memory usage in the process viewer?
For memory leaks take a look at: http://www.outofhanwell.com/ieleak/ and http://dbaron.org/log/2006-01#d20060114.
Those two links are two good places to start.

Eric
See: http://batalion.blogspot.com/2006/03/ajax-connection-pool.html
Is there any data out there that would suggest connection pooling is necessary? I'm a Java guy originally myself and pooling makes a lot of sense for very heavyweight objects. Is XHR such a beast?

The browser already queues up requests since IE can only make 2 at a time since we can only have 2 open connections to a site. Same reason images take a while to load on a page.

So if we just fire requests than it will just queue up until a connection is open. But this does not guarntee a loading order so one can load before the next.

By adding a coded queue you can make sure that the browser maintains order and you can reuse the object.

Now the reason why the XMLHttpRequest is "heavy" as Derek said is the browser's bad garbage collection. Memory leaks are not uncommon with poorly written code so we need to make sure we eliminate every little memory hole.

Eric

I think writing something like this would be a fun little side project. Does anybody have any ideas about what the interface should be? No UML diagrams necessary or anything, but what methods would you expect? Would a constructor which takes the number of XHR connections and a queue() method suffice?
Eric, I notice that you call the on readystatechange handler before open. I did that on my code and found it to be an issue within IE. Maybe it was my code, not sure. Is there a standard in writing AJAX? Maybe there should be?
Scott,

If you follow that bold link I posted in the very first line I wrote, it says what the problem was with it and how to fix it. It has already been pointed out to me.

Eric
Thanks for your awesome article. You helped me work out some bugs in my site. -Josh
We have another wierd issue when we try to abort a XHR before its complete, i.e when we close the window before the request comes back. After two times that we close the window, the XHR simply doesnt seem to respond. We need to shut down all the instances of IE to overcome this. Has anyone faced this before?
I have the same issue. Closing the window before a request comes back, causes the web pages to stop responding. We also need to start a new session to overcome this. Has anyone found a solution yet?
req_fail_IE.onreadystatechange = req_fail_IE.onreadystatechange = processReqChange_fail_IE; when ever we call a function we have to append parenthsis'()' at the end . Then wat happend to the processReqChange_fail_IE;. It did't had the braces
cuchandoo, when you do not include (), you are assigning a reference to a function. If we would include (), the code inside that function would execute right than and there. We do not want that to happen. We want the code to execute when the XMLHttpRequest states change. Eric


Add a comment

Title
Body
HTML : b, i, blockquote, br, p, pre, a href="", ul, ol, li
Math Quiz 8 + 1 = (Helps stop blog spam)
Name
E-mail address
Website
Remember me Yes  No 

E-mail addresses are not publicly displayed, so please only leave your e-mail address if you would like to be notified when new comments are added to this blog entry (you can opt-out later).

TrackBack to http://radio.javaranch.com/pascarello/addTrackBack.action?entry=1143735438047