Working with Multiple Concurrent XMLHttpRequest Requests


As you may recall, Chapter 3 ended with a lunch example that let users download their choice of menus using Ajax. There’s a subtle flaw with that example: it displayed two buttons, one for lunch menu 1, the other for lunch menu 2. Clicking button 1 displayed the items in menu 1 in the drop-down list, and clicking button 2 displayed the items in menu 2.

So far, so good. But there’s a problem. What if the user gets impatient and clicks button 2 after clicking button 1 but doesn’t wait until the menu data for button 1 is downloaded? The lunch example used only one XMLHttpRequest object to connect to the server, so when the response comes back from the server, which request is the response for? Taking a look at the code reveals the problem: there’s only one XMLHttpRequest object, but two buttons that make use of it:

 <html>   <head>     <title>Using Ajax with XML</title>     <script language = "javascript">       var menu;       var XMLHttpRequestObject = false;       if (window.XMLHttpRequest) {         XMLHttpRequestObject = new XMLHttpRequest();         XMLHttpRequestObject.overrideMimeType("text/xml");       } else if (window.ActiveXObject) {         XMLHttpRequestObject = new           ActiveXObject("Microsoft.XMLHTTP");       }       function getmenu1()       {         if(XMLHttpRequestObject) {           XMLHttpRequestObject.open("GET", "menu1.xml");           XMLHttpRequestObject.onreadystatechange = function()           {             if (XMLHttpRequestObject.readyState == 4 &&                 XMLHttpRequestObject.status == 200) {               var xmlDocument = XMLHttpRequestObject.responseXML;               menu = xmlDocument.getElementsByTagName("menuitem");               listmenu();               }             }             XMLHttpRequestObject.send(null);           }       }       function getmenu2()       {         if(XMLHttpRequestObject) {           XMLHttpRequestObject.open("GET", "menu2.xml");           XMLHttpRequestObject.onreadystatechange = function()           {             if (XMLHttpRequestObject.readyState == 4 &&               XMLHttpRequestObject.status == 200) {             var xmlDocument = XMLHttpRequestObject.responseXML;             menu = xmlDocument.getElementsByTagName("menuitem");             listmenu();             }           }           XMLHttpRequestObject.send(null);         }       }       function listmenu ()       {         var loopIndex;         var selectControl = document.getElementById('menuList');         for (loopIndex = 0; loopIndex < menu.length; loopIndex++ )         {            selectControl.options[loopIndex] = new               Option(menu[loopIndex].firstChild.data);         }     }     function setmenu()     {       document.getElementById('targetDiv').innerHTML =         "You selected " + menu[document.getElementById           ('menuList').selectedIndex].firstChild.data;     }     </script>   </head>   <body>     <h1>Using Ajax with XML</h1>     <form>       <select size="1"          onchange="setmenu()">         <option>Select a menu item</option>       </select>       <br>       <br>       <input type = "button" value = "Select menu 1"         onclick = "getmenu1()">       <input type = "button" value = "Select menu 2"         onclick = "getmenu2()">     </form>     <div  width =100 height=100>Your lunch       selection will appear here.</div>   </body> </html>

You can see the issue: both getmenu1 and getmenu2 use the same XMLHttpRequest object, which is probably fine for most purposes, but not all. You can run into cases where the response from the server is slow, and an impatient user clicks the other button. And then you’re not sure which XMLHttpRequest the server is responding to.

So what’s the answer? One solution is to use multiple XMLHttpRequest objects.

Using multiple XMLHttpRequest objects

One solution to the problem of having multiple choices is to create one XMLHttpRequest object per request. For example, you might modify lunch.html into, for example, double.html, which creates two XMLHttpRequest objects: XMLHttpRequestObject and XMLHttpRequestObject2, like this:

 var XMLHttpRequestObject = false; if (window.XMLHttpRequest) {   XMLHttpRequestObject = new XMLHttpRequest();   XMLHttpRequestObject.overrideMimeType("text/xml"); } else if (window.ActiveXObject) {   XMLHttpRequestObject = new     ActiveXObject("Microsoft.XMLHTTP"); } var XMLHttpRequestObject2 = false; if (window.XMLHttpRequest2) {   XMLHttpRequestObject2 = new XMLHttpRequest();   XMLHttpRequestObject2.overrideMimeType("text/xml"); } else if (window.ActiveXObject) {   XMLHttpRequestObject2 = new     ActiveXObject("Microsoft.XMLHTTP"); }

This way, when the user clicks one button, one XMLHttpRequest object responds. Because there are two buttons and two XMLHttpRequest objects, you’ve improved the odds that you won’t have any confusion between requests. Here’s what the new code, double.html, looks like:

 <html>   <head>     <title>Using two XMLHttpRequest objects</title>     <script language = "javascript">       var menu;       var XMLHttpRequestObject = false;       if (window.XMLHttpRequest) {         XMLHttpRequestObject = new XMLHttpRequest();         XMLHttpRequestObject.overrideMimeType("text/xml");       } else if (window.ActiveXObject) {         XMLHttpRequestObject = new           ActiveXObject("Microsoft.XMLHTTP");       }       var XMLHttpRequestObject2 = false;       if (window.XMLHttpRequest2) {         XMLHttpRequestObject2 = new XMLHttpRequest();         XMLHttpRequestObject2.overrideMimeType("text/xml");       } else if (window.ActiveXObject) {         XMLHttpRequestObject2 = new           ActiveXObject("Microsoft.XMLHTTP");       }       function getmenu1()       {         if(XMLHttpRequestObject) {           XMLHttpRequestObject.open("GET", "menu1.xml");           XMLHttpRequestObject.onreadystatechange = function()           {             if (XMLHttpRequestObject.readyState == 4 &&               XMLHttpRequestObject.status == 200) {             var xmlDocument = XMLHttpRequestObject.responseXML;             menu = xmlDocument.getElementsByTagName("menuitem");             listmenu();             }           }           XMLHttpRequestObject.send(null);         }       }       function getmenu2()       {         if(XMLHttpRequestObject2) {           XMLHttpRequestObject2.open("GET", "menu2.xml");           XMLHttpRequestObject2.onreadystatechange = function()           {             if (XMLHttpRequestObject2.readyState == 4 &&               XMLHttpRequestObject2.status == 200) {             var xmlDocument = XMLHttpRequestObject2.responseXML;             menu = xmlDocument.getElementsByTagName("menuitem");             listmenu();             }           }           XMLHttpRequestObject2.send(null);         }       }       function listmenu ()       {         var loopIndex;         var selectControl = document.getElementById('menuList');         for (loopIndex = 0; loopIndex < menu.length; loopIndex++ )         {             selectControl.options[loopIndex] = new                Option(menu[loopIndex].firstChild.data);         }     }     function setmenu()     {       document.getElementById('targetDiv').innerHTML =         "You selected " + menu[document.getElementById           ('menuList').selectedIndex].firstChild.data;     }      </script>    </head>    <body>      <h1>Using two XMLHttpRequest objects</h1>      <form>        <select size="1"           onchange="setmenu()">          <option>Select a menu item</option>        </select>        <br>        <br>        <input type = "button" value = "Select menu 1"          onclick = "getmenu1()">        <input type = "button" value = "Select menu 2"          onclick = "getmenu2()">      </form>      <div  width =100 height=100>Your lunch        selection will appear here.</div>   </body> </html>

This way of doing things works, as you can see in Figure 4.1.

image from book
Figure 4.1: Using two XMLHttpRequest objects

Even this solution is somewhat problematic, however. You don’t really want to have to write explicit code for each XMLHttpRequest object; what if your application used a hundred such objects? A better way of doing things is to store the objects in an array and create new XMLHttpRequest objects as needed.

Storing XMLHttpRequest objects in an array

Creating two XMLHttpRequest objects can alleviate the problem of multiple concurrent requests, but what if you need dozens of such objects? One solution is to place them in an object array, as shown in objectarray.html.

You can start by creating an array of XMLHttpRequest objects named XMLHttpRequestObjects:

 <script language = "javascript">   var menu;   var XMLHttpRequestObjects = new Array();     .     .     .

Then, in getmenu1 and getmenu2, you can create a new XMLHttpRequest object and add it to the XMLHttpRequestObjects array using that array’s built-in push method (which adds an object to the end of the array), as well as adding the index of the current XMLHttpRequest object to the array:

 function getmenu1() {   if (window.XMLHttpRequest) {     XMLHttpRequestObjects.push(new XMLHttpRequest());   } else if (window.ActiveXObject) {     XMLHttpRequestObjects.push(new       ActiveXObject("Microsoft.XMLHTTP"));     }     index = XMLHttpRequestObjects.length - 1;     .     .     .

From then on, you can refer to the new XMLHttpRequest object as XMLHttpRequestObjects[index] and you’re guaranteed to have a fresh XMLHttpRequest object for each request. Here’s how that works in objectarray.html:

 <html>   <head>     <title>Using an array of XMLHTTPRequest objects</title>     <script language = "javascript">       var menu;       var index = 0;       var XMLHttpRequestObjects = new Array();       function getmenu1()       {         if (window.XMLHttpRequest) {           XMLHttpRequestObjects.push(new XMLHttpRequest());         } else if (window.ActiveXObject) {         XMLHttpRequestObjects.push(new           ActiveXObject("Microsoft.XMLHTTP"));         }         index = XMLHttpRequestObjects.length - 1;         if(XMLHttpRequestObjects[index]) {           XMLHttpRequestObjects[index].open("GET", "menu1.xml");           XMLHttpRequestObjects[index].onreadystatechange =           function()           {             if (XMLHttpRequestObjects[index].readyState == 4 &&               XMLHttpRequestObjects[index].status == 200) {             var xmlDocument =               XMLHttpRequestObjects[index].responseXML;             menu = xmlDocument.getElementsByTagName("menuitem");             listmenu();             }           }           XMLHttpRequestObjects[index].send(null);         }     }     function getmenu2()     {       if (window.XMLHttpRequest) {         XMLHttpRequestObjects.push(new XMLHttpRequest());       } else if (window.ActiveXObject) {       XMLHttpRequestObjects.push(new         ActiveXObject("Microsoft.XMLHTTP"));       }       index = XMLHttpRequestObjects.length - 1;       if(XMLHttpRequestObjects[index]) {         XMLHttpRequestObjects[index].open("GET", "menu2.xml");         XMLHttpRequestObjects[index].onreadystatechange =         function()         {           if (XMLHttpRequestObjects[index].readyState == 4 &&             XMLHttpRequestObjects[index].status == 200) {           var xmlDocument =           XMLHttpRequestObjects[index].responseXML;           menu = xmlDocument.getElementsByTagName("menuitem");           listmenu();           }         }         XMLHttpRequestObjects[index].send(null);       }     }     function listmenu ()     {       var loopIndex;       var selectControl = document.getElementById('menuList');       for (loopIndex = 0; loopIndex < menu.length; loopIndex++ )       {           selectControl.options[loopIndex] = new              Option(menu[loopIndex].firstChild.data);       }    }    function setmenu()    {       document.getElementById('targetDiv').innerHTML =         "You selected " + menu[document.getElementById           ('menuList').selectedIndex].firstChild.data;    }    </script>  </head>  <body>    <h1>Using an array of XMLHTTPRequest objects</h1>    <form>      <select size="1"         onchange="setmenu()">        <option>Select a menu item</option>      </select>      <br>      <br>      <input type = "button" value = "Select menu 1"        onclick = "getmenu1()">      <input type = "button" value = "Select menu 2"        onclick = "getmenu2()">    </form>    <div  width =100 height=100>Your lunch     selection will appear here.</div>   </body> </html>

This way of doing things also works, as you can see in Figure 4.2.

image from book
Figure 4.2: Using an array of XMLHttpRequest objects

Creating an array of XMLHttpRequest objects like this lets you handle multiple XMLHttp requests without getting them all mixed up. But this is a little awkward, and you’re going to end up with a big array of XMLHttpRequest objects unless you start deleting them from the array when they’re no longer needed.

It turns out that there’s a better way to handle multiple XMLHttpRequest objects than even an array of such objects, and that’s using inner functions in JavaScript.

Using inner functions

The way you usually handle multiple XMLHttpRequest requests is through the use of inner functions in Ajax. A JavaScript inner function is just a function defined inside another function. Here’s an example, where the function named inner is an inner function:

 function outer(data) {   var variable1 = data;   function inner(variable2)   {     alert(variable1 + variable2)   } }

Now say you call the outer function with a value of 4 like this: outer(4). That sets the variable variable1 in this function to 4. The inner function has access to the outer function’s data, even after the call to the outer function has finished. So if you were now to call the inner function, passing a value of 5, that would set variable2 in the inner function to 5-and variable1 is still set to 4. So the result of calling the inner function would be 4+5=9, which is the value that the JavaScript alert function would display in this case.

Here’s the good part: every time you call the outer function, a new copy of that function is created, which means a new value will be stored as variable1. And the inner function will have access to that value. So if you make the shift from thinking in terms of variable1 and start thinking in terms of the variable XMLHttpRequestObject, you can see that each time a function like this is called, JavaScript creates a new copy of the function with a new XMLHttpRequest object, and that object is available to any inner functions.

That’s what you want in this case, because the code you’ve been writing already uses an anonymous inner function, connected to the onreadystatechange property in the getData function. Here’s how it works: the XMLHttpRequest object is created, and then it’s used inside the anonymous inner function this way:

 var XMLHttpRequestObject = false; if (window.XMLHttpRequest) {   XMLHttpRequestObject = new XMLHttpRequest(); } else if (window.ActiveXObject) {   XMLHttpRequestObject = new     ActiveXObject("Microsoft.XMLHTTP"); } function getData(dataSource, divID) {   if(XMLHttpRequestObject) {     var obj = document.getElementById(divID);     XMLHttpRequestObject.open("GET", dataSource);     XMLHttpRequestObject.onreadystatechange = function()     {       if (XMLHttpRequestObject.readyState == 4 &&         XMLHttpRequestObject.status == 200) {           obj.innerHTML =             XMLHttpRequestObject.responseText;       }     }     XMLHttpRequestObject.send(null);   } }

To use a new XMLHttpRequest object for each request, move the section of the code where the XMLHttpRequest object is created inside the getData function, because the getData function is the outer function that encloses the anonymous inner function. Doing so creates a new XMLHttpRequest object to be used by the anonymous inner function each time getData is called. And each time getData is called, a new copy of getData is created. That’s what you want: a new XMLHttpRequest object for each new request, created automatically.

Here’s what it looks like in code:

 function getData(dataSource, divID) {   var XMLHttpRequestObject = false;   if (window.XMLHttpRequest) {     XMLHttpRequestObject = new XMLHttpRequest();   } else if (window.ActiveXObject) {     XMLHttpRequestObject = new       ActiveXObject("Microsoft.XMLHTTP");   }   if(XMLHttpRequestObject) {     var obj = document.getElementById(divID);     XMLHttpRequestObject.open("GET", dataSource);     XMLHttpRequestObject.onreadystatechange = function()     {       if (XMLHttpRequestObject.readyState == 4 &&         XMLHttpRequestObject.status == 200) {           obj.innerHTML =             XMLHttpRequestObject.responseText;       }     }     XMLHttpRequestObject.send(null);   } }

That’s it-you’re done. By moving the creation code inside the getData function, this application can now handle multiple concurrent XMLHttpRequest requests, no matter how many times users click the various buttons in your Web page. Each time, the getData function is called, a new copy of the function is created, with a new XMLHttpRequest object, and you’re good to go.

This is perfect for the lunch menu application, so perfect that you don’t even need two functions, getmenu1 and getmenu2, anymore (because you don’t need to rely on separate functions creating separate XMLHttpRequest objects). All you need is one function, getmenu, in this new version of the lunch application, inner.html:

 <html>   <head>     <title>Using multiple XMLHttpRequest objects</title>     <script language = "javascript">       var menu;       var XMLHttpRequestObject = false;       if (window.XMLHttpRequest) {         XMLHttpRequestObject = new XMLHttpRequest();         XMLHttpRequestObject.overrideMimeType("text/xml");       } else if (window.ActiveXObject) {         XMLHttpRequestObject = new           ActiveXObject("Microsoft.XMLHTTP");       }       function getmenu(menuNumber)       {         if(XMLHttpRequestObject) {           XMLHttpRequestObject.open("GET", "menus.php?menu=" +             menuNumber);           XMLHttpRequestObject.onreadystatechange = function()           {             if (XMLHttpRequestObject.readyState == 4 &&               XMLHttpRequestObject.status == 200) {             var xmlDocument = XMLHttpRequestObject.responseXML;             menu = xmlDocument.getElementsByTagName("menuitem");             listmenu();             }           }           XMLHttpRequestObject.send(null);         }       }       function listmenu ()       {         var loopIndex;         var selectControl = document.getElementById('menuList');         for (loopIndex = 0; loopIndex < menu.length; loopIndex++ )         {             selectControl.options[loopIndex] = new                Option(menu[loopIndex].firstChild.data);         }       }       function setmenu()       {         document.getElementById('targetDiv').innerHTML =           "You selected " + menu[document.getElementById             ('menuList').selectedIndex].firstChild.data;       }     </script>   </head>   <body>     <h1>Using multiple XMLHttpRequest objects</h1>     <form>       <select size="1"          onchange="setmenu()">         <option>Select a menu item</option>       </select>       <br>       <br>       <input type = "button" value = "Select menu 1"         onclick = "getmenu('1')">       <input type = "button" value = "Select menu 2"         onclick = "getmenu('2')">     </form>     <div  width =100 height=100>Your lunch      selection will appear here.</div>   </body> </html>

To handle multiple concurrent XMLHttpRequest requests, just move the XMLHttpRequest object inside the getmenu function, and you’re done with inner.html:

 <html>   <head>     <title>Using multiple XMLHttpRequest objects</title>     <script language = "javascript">       var menu;       function getmenu(menuNumber)       {         var XMLHttpRequestObject = false;         if (window.XMLHttpRequest) {           XMLHttpRequestObject = new XMLHttpRequest();           XMLHttpRequestObject.overrideMimeType("text/xml");         } else if (window.ActiveXObject) {           XMLHttpRequestObject = new             ActiveXObject("Microsoft.XMLHTTP");         }           if(XMLHttpRequestObject) {             XMLHttpRequestObject.open("GET", "menus.php?menu=" +               menuNumber);             XMLHttpRequestObject.onreadystatechange = function()             {               if (XMLHttpRequestObject.readyState == 4 &&                 XMLHttpRequestObject.status == 200) {               var xmlDocument = XMLHttpRequestObject.responseXML;               menu = xmlDocument.getElementsByTagName("menuitem");               listmenu();               }             }             XMLHttpRequestObject.send(null);           }         }         function listmenu ()         {           var loopIndex;           var selectControl = document.getElementById('menuList');           for (loopIndex = 0; loopIndex < menu.length; loopIndex++ )           {               selectControl.options[loopIndex] = new                  Option(menu[loopIndex].firstChild.data);           }      }      function setmenu()      {        document.getElementById('targetDiv').innerHTML =          "You selected " + menu[document.getElementById            ('menuList').selectedIndex].firstChild.data;      }      </script>    </head>    <body>      <h1>Using multiple XMLHttpRequest objects</h1>      <form>        <select size="1"           onchange="setmenu()">          <option>Select a menu item</option>        </select>        <br>        <br>        <input type = "button" value = "Select menu 1"          onclick = "getmenu('1')">        <input type = "button" value = "Select menu 2"          onclick = "getmenu('2')">      </form>      <div  width =100 height=100>Your lunch       selection will appear here.</div>   </body> </html>

And here’s the PHP script this application interacts with, menus.php:

 <? header("Content-type: text/xml"); if ($_GET["menu"] == "1")   $menuitems = array('Ham', 'Turkey', 'Beef'); if ($_GET["menu"] == "2")   $menuitems = array('Tomato', 'Cucumber', 'Rice'); echo '<?xml version="1.0" ?>'; echo '<menu>'; foreach ($menuitems as $value) {   echo '<menuitem>';   echo $value;   echo '</menuitem>'; } echo '</menu>'; ?>

You can see the end result in Figure 4.3. Feel free to click the buttons as many times as you like; the XMLHttpRequests you send to the server won’t get confused.

Note 

Although this technique keeps XMLHttpRequest requests separate, there’s still no guarantee that the server won’t respond to those requests out of order, especially if the user is into rapid-fire button clicking. There’s a technique that Ajax developers sometimes use to han-dle this problem: instead of executing code to handle (possibly out-of-order) data sent back from the server, they send back the actual JavaScript to execute. In many cases, this technique can make sure that your application does the right thing. It’s also a technique used by many big-time online applications with Application Programming Interfaces (APIs) that let you connect to them.

Cross Ref 

You’ll see how to connect to Google Suggest using such an API later in this chapter. Google Suggest sends back JavaScript for you to execute, so it’s worthwhile getting to know how to handle that kind of Ajax result.

image from book
Figure 4.3: Using multiple XMLHttpRequest objects



Ajax Bible
Ajax Bible
ISBN: 0470102632
EAN: 2147483647
Year: 2004
Pages: 169

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net