Schneimi’s Dev Weblog


Multiple ajax requests problems and AjaxQueue as solution

Posted in Ajax, CakePHP by schneimi on the March 10, 2008
Tags: , , , ,

For my app I had to load many import processes at once by Ajax requests, so I ran into some serious problems.

1. Session data was not available each second request
I used the database option for Sessions, and that seemed to be the problem in this case. Because I don’t worry much about how sessions are saved, I changed it to cake in core.php and it solved this problem, not a really good solution, but I’m fine with it.

2. Timed out Socket connections
During the import process I had to make some Socket Connections and however there suddenly was no connection possible anymore after 10-15 requests, so the following ran into timeout.

3. The solution: AjaxQueue
After some search and search and search, I finally found a script called AjaxQueue posted on a mailing list. There you can set the maximum amout of simultaneous Ajax requests, exactly what I was looking for. After some testing it turned out to do a really wonderful job and all my problems were solved without loosing much of performance.

The following code is for the Prototype framework, but it should be no problem to adapt it to other frameworks in replacing the “Ajax.”-statements to similar ones of another framework.

var AjaxQueue = {
	batchSize: 1, //No.of simultaneous AJAX requests allowed, Default : 1
	urlQueue: [], //Request URLs will be pushed into this array
	elementsQueue: [], //Element IDs of elements to be updated on completion of a request ( as in Ajax.Updater )
	optionsQueue: [], //Request options will be pushed into this array
	setBatchSize: function(bSize){ //Method to set a different batch size. Recommended: Set batchSize before making requests
		this.batchSize = bSize;
	},
	push: function(url, options, elementID){ //Push the request in the queue. elementID is optional and required only for Ajax.Updater calls
		this.urlQueue.push(url);
		this.optionsQueue.push(options);
		if(elementID!=null){
			this.elementsQueue.push(elementID);
		} else {
			this.elementsQueue.push("NOTSPECIFIED");
		}

		this._processNext();
	},
	_processNext: function() { // Method for processing the requests in the queue. Private method. Don't call it explicitly
		if(Ajax.activeRequestCount < AjaxQueue.batchSize) // Check if the currently processing request count is less than batch size
		{
			if(AjaxQueue.elementsQueue.first()=="NOTSPECIFIED") { //Check if an elementID was specified
				// Call Ajax.Request if no ElementID specified
				//Call Ajax.Request on the first item in the queue and remove it from the queue
				new Ajax.Request(AjaxQueue.urlQueue.shift(), AjaxQueue.optionsQueue.shift()); 

				var junk = AjaxQueue.elementsQueue.shift();
			} else {
				// Call Ajax.Updater if an ElementID was specified.
				//Call Ajax.Updater on the first item in the queue and remove it from the queue
				new Ajax.Updater(AjaxQueue.elementsQueue.shift(), AjaxQueue.urlQueue.shift(), AjaxQueue.optionsQueue.shift());
			}
		}
	}
};
Ajax.Responders.register({
  //Call AjaxQueue._processNext on completion ( success / failure) of any AJAX call.
  onComplete: AjaxQueue._processNext
});

/************* SYNTAX ***************
AjaxQueue.setBatchSize(size);

AjaxQueue.push(URL , OPTIONS, [ElementID]);

************** USAGE ***************
AjaxQueue.setBatchSize(4);
AjaxQueue.push("http://www.testingqueue.com/process/",{onSucess: funcSuccess, onfailure: funcFailure});
AjaxQueue.push("http://www.testingqueue.com/process1/",{onSucess: funcSuccess1, onfailure: funcFailure1}, "myDiv");
AjaxQueue.push("http://www.testingqueue.com/process2/",{onSucess: funcSuccess2, onfailure: funcFailure2});
AjaxQueue.push("http://www.testingqueue.com/process3/",{onSucess: funcSuccess3, onfailure: funcFailure3});
AjaxQueue.push("http://www.testingqueue.com/process4/",{onSucess: funcSuccess4, onfailure: funcFailure4});
AjaxQueue.push("http://www.testingqueue.com/process5/",{onSucess: funcSuccess5, onfailure: funcFailure5});
**********************************/

11 Responses to 'Multiple ajax requests problems and AjaxQueue as solution'

Subscribe to comments with RSS or TrackBack to 'Multiple ajax requests problems and AjaxQueue as solution'.

  1. Jeremiahjx said,

    well done, bro


  2. Hello compliment for your site and articles..
    CAn you give me some example in ajax cake to make a mailing list…
    I need that every email sent, I recieve feedback…
    Is it possible?
    Thanks.

  3. schneimi said,

    Hi,

    This should be no problem, you just have to loop through your list of mails and push the ajax-action that sends the mail into the AjaxQueue. I give you my code as example, it’s basically the same, I just loop through a list of media-sources(paths) and import them. The import is indicated on the website in a progress bar:

    The code is from the controller-action that is called when I press the import-button, in your case it’s the send-button.

    foreach ($newMedia as $path) {
    if (!empty($path)) {
    $path = urlencode($path);

    echo $javascript->codeBlock(“$(’source_”.$source['Source']['id'].”_progress’).innerHTML = ‘pending’;”
    .”AjaxQueue.push(’sources/import”.$type.”/”.$id.”/?path=”.$path.”‘, ”
    .”{onSuccess:function(){updateProgress(’source_”.$id.”‘, “.$importCounter++.”, “.count($newMedia).”);}}, ’source_”.$id.”‘)”);
    }
    }

    Hope this helps!

  4. morris said,

    Is this only for prototype? Please specify ;)

  5. schneimi said,

    Thanks for your comment, see the changes. ;-)

  6. Luca said,

    Is there a way to keep trace of the request’s ids and use them onSuccess?

    I am sending multiple requests using this script andloading urls from a csv I’d like to place on the success function the url’s id of each single request in order to identify the url used for each response.

    I am trying to use the elementID to set the id of the pushed url and retrieve it onSuccess, but it doesn’t respect the order!!

    Thanks in advance.

    • schneimi said,

      If I understand you right, you want to have an id of your request url be known in the onSuccess function, so that you can identify an ending request.

      I think you have to set it in the onSuccess function when writing it, so I would try something like this (in PHP):

      echo "AjaxQueue.push('".$url."/".$id."', {onSuccess:function(){alert('".$id."');}}, elementID);";
      

      Hope this helps.

  7. boopathi said,

    hi, its really useful


  8. This is quite a hot information. I’ll share it on Delicious.

  9. Bindi said,

    Can you please help me with my problem ?

    When an user tries to send AJAX requests simultaneously from multiple browser tabs, one request get completed and the page loads but the other AJAX calls are preempted. AS a result of which the response is empty for the other calls. Only one call survives. In my application using struts 2.0, JSP and javascript and the prototype framework, i found that the server response is empty in the cases mentioned above though the data gets updated in teh database with the request parameters. The onSucess event handler for Ajax.request gets called but the the response is empty.

    Can you please help?

    Thanks

    • schneimi said,

      Your problem doesn’t sound like a javascript/ajax problem, because the calls should run in different environments each browser tab, and they obviously get properly executed.

      I can only guess that there is a problem within the function you are calling. Maybe the database gets read locked while writing, or anything else that prevents the view from rendering for the following calls. But I am not familiar with struts and JSP, so I doubt I can help you with this.


Leave a Reply