tadhg.com
tadhg.com
 

Handling Multiple AJAX Requests

23:28 Sat 25 Nov 2006. Updated: 21:42 28 Nov 2006
[, , , ]

In working on an enhancement to my card price list sorting bookmarklet, I ran into a problem: if you want to collate data from a number of sources asynchronously, combine the data, then do something with all of it, how do you do that in JavaScript?

This link covers simultaneous requests, but that didn’t solve my problem, although it helped me along.

I wanted to go to a www.magictraders.com pricelist page, invoke a bookmarklet with an arbitrary number of keywords, and have the bookmarklet grab data from a page based on each keyword, then combine that data with the pricelist on the page in question, and finally turn the whole thing into a sortable table (the last bit I already had code for, thanks to the previous version of the bookmarklet).

My solution involved some JavaScript closure voodoo. It took me long enough to figure out that I think it worthwhile to share a simplified example.

Assume we want to combine the contents of several pages and then show the combined contents in a single JavaScript alert. We don’t want to show multiple alerts, or to show an incomplete alert. Also assume that we don’t know in advance how many different pages we want to combine. For this example, our data comes as text or HTML and not XML. Also, for this example, I haven’t tested in IE, although the overall structure should work fine in it. I adapted some of the code here from https://blueprints.dev.java.net/ajax-faq.html#concurrent_requests.

First, the skeleton of our various functions:

combinedAlert = {
                
        init: function(urlArray) {
        },
                
        requestWrapper: function(urlArray, theContent) {
        /*The closure inside which we place the XMLHttpRequest call*/
                
        },
                
        takeText: function(urlArray, theContent, responseText) {
        /*What gets called after each AJAX request completes*/
                
        },
                
        doAlert: function(theContent) {
         /*What gets called when the last AJAX request completes*/
                
        }
        
}

We’ll pass the init() function an arbitrarily-sized array of URLs, and it doesn’t need to do much except declare theContent and call requestWrapper():

init: function(urlArray) {
        theContent = "";
        combinedAlert.requestWrapper(urlArray, theContent);
},

requestWrapper(), on the other hand, involves a lot more:

requestWrapper: function(urlArray, theContent) {

        requestObject = makeRequestObject();
        requestObject.onreadystatechange = processRequest;
        /*(Defined below, as functions inside requestWrapper*/

        var url = urlArray[0];
        /*Get the first url out of the array*/
                
        requestObject.open("GET", url, true);
        requestObject.send(null);
        /*Does the actual opening of the connection*/

        function makeRequestObject() {
        /*This function forks for IE and returns the XMLHttpRequest object.*/
                if (window.XMLHttpRequest) {
                        return new XMLHttpRequest();
                } else if (window.ActiveXObject) {
                        return new ActiveXObject("Microsoft.XMLHTTP");
                }
        };
                
        function processRequest() {
        /*This function gets called when the XMLHttpRequest object reports a change in its state*/
                if (requestObject.readyState == 4) {
                /*We only care if it reports the state as 'finished'*/
                        if (requestObject.status == 200) {
                        /*We only want to support actual page loads, not 404s etc.*/
                                combinedAlert.takeText(urlArray, theContent, requestObject.responseText);
                                /*Here we pass the parameters to the requestWrapper function, along with the text from the page we grabbed asynchronously, to takeText()*/
                        }
                }

        };

},

Lexical closures make this possible—without them, passing parameters to any event handlers would involve a lot of messy global variables at best, and might prove extremely difficult even then. The basic form of this kind of closure looks like this:

myFunction(parameters, someEvent) {
        someEvent.hook = handleEvent;

        function handleEvent() {
                doSomethingWithParameters(parameters);
                /*Ha! I have access to the parameters even though you can't pass them to event handlers!*/
        }
};

With requestWrapper, we expand on this a little to pass the parameters around between functions. This gets more interesting with takeText(), which manipulates theContent and kicks off some recursion:

takeText: function(urlArray, theContent, responseText) {
         theContent += responseText;
         /*The basic operation we want to do, adding what we got from the asynchronous call to our theContent variable*/

         urlArray.shift();
         /*since we've gotten this far, we must have finished with the loading of the URL in the position urlArray[0], which we set the XMLHttpRequest object to fetch from in requestWrapper. So we remove it from the array.*/

         if (urlArray.length > 0) {
         /*If, after the shift, we still have URLs to process*/
                 combinedAlert.requestWrapper(urlArray, theContent);
                 /*The core of the trickiness. We now send the altered urlArray and theContent variables back to requestWrapper, which will in turn load the next URL and kick off takeText() again with the responseText from that page load...*/
         } else {
         /*Or, if we have no more URLs to process*/
                 combinedAlert.doAlert(theContent);
                 /*doAlert doesn't care about anything except what we want it to alert*/
                 }

},

Finally, the rather simple doAlert() function. I kept this simple, but obviously we could do all kinds of things to theContent here before popping up the alert:

doAlert: function(theContent) {
        alert(theContent);
}

Combining it all gives us this script. To test it out, try these links:
One URL
Two URLs
Three URLs
Four URLs

« (previous)

3 Responses to “Handling Multiple AJAX Requests”

  1. sonai Says:

    I will not see properly your code in my browser

  2. Tadhg Says:

    Hmm, that’s odd. What browser are you using? On what platform? Also, is it not showing up at all, or going off the edges, or what? Let me know and I’ll try to fix it.

  3. bone boy Says:

    tis a good solution. i found this very useful. only tested in ff2 & 3 so far.

Leave a Reply