19

I'm wondering how to make ajax calls in groups of n.

Here's my use case:

I have a table that displays usage data. You can drill into each row, and if each row has a common attribute you can drill deeper into, you can choose to drill into all of them at once. For each row, an ajax call is made to fetch the data to be appended to the table.

In certain cases there can be up to 50 rows to drill into simultaneously. As you can imagine, that puts a lot of stress on the server. How best can I send these calls in smaller batches that wait on the batch before them to fire off?

I know there are plugins like jquery message queuing that could potentially help me out, but this is a work project so we'd like to avoid plugins as much as possible.

stinkycheeseman
  • 43,437
  • 7
  • 30
  • 49
  • 1
    so make your own queue manager :) – zb' Aug 08 '11 at 18:10
  • This answer generally helps http://stackoverflow.com/questions/42425885/trying-to-make-2-ajax-calls-via-jquery-and-then-prepending-the-data-taken-from/42426722#42426722 – Mehdi Raash Feb 23 '17 at 21:59

4 Answers4

19

You can take a look at using jQuery.when, which allows you to execute callback functions when all requests have completed.

$.when($.ajax("request1"), $.ajax("request2"), $.ajax("request3"))
 .done(function(data1,  data2, data3){
         // Do something with the data
 });

Or

$.when($.ajax("request1"), $.ajax("request2"), $.ajax("request3"))
.then(successCallback, errorHandler);

See the following post for more information.

Also, I'm not sure that your apprehension towards using a plugin should be affected by the fact that you are in a work environment, especially if it simplifies your work. Hence allowing to be more productive. Granted you have to choose your plugins carefully, since quality and long term maintenance can be a issue.

Community
  • 1
  • 1
Garett
  • 16,632
  • 5
  • 55
  • 63
  • 1
    The jQuery [Deferred object](http://api.jquery.com/category/deferred-object/) is definitely the way to go when handling asynchronous requests. The use of $.when and $.done when dealing with $.ajax promise objects and code yielded by a setTimeout has made my code cleaner and more readable. Good call. – stinkycheeseman Dec 31 '12 at 15:08
  • Yes understand $.when() but it looks cumbersone to use for my case which is 5K xhr2 queries, sent it batches of 50. (Chrom throws a memory fault with SPDY servers), Chromium folks will eventually fix, but I need throttling now. I think I might try the recursion suggestion below. Does that sound right? – Dr.YSG Jun 19 '13 at 14:59
7

Ajax calls using jQuery are typically asynchronous. So, if you have 50 rows, jQuery will asynchronously send all 50 requests--you don't get to control the sequence of processing when you get responses from the server.

You can use async: false on the $.ajax call such that only one request is sent to the server as you loop through your rows:

$.ajax({
    url: location,
    data: params,
    async: false,
    success: function(msg) { // do something}
});

The issue with this approach (async: false) is that user may experience a "freeze" or unresponsive page.

Another way is to use recursion in your JavaScript so that the calls are still asynchronous but the ajax call still waits for the success event of each row like the following:

var maxRows = 50;

function myFunc(index) {
   $.ajax({
       url: location,
       data: params,
       async: true,
       success: function(msg) { 
            if (index < maxRows) {
               // do something
            }
            else {
               return; //index equals maxRows--stop the recursion
            }
            index++;
            myFunc(index); //call the function again
       }
   });

   $(document).ready(function() {
       myFunc(0);      
   });
}
pimvdb
  • 151,816
  • 78
  • 307
  • 352
spdeveloper
  • 301
  • 1
  • 4
  • I need to do 5K xhr2 queries, sent it batches of 50. (Chrome throws a memory fault with SPDY servers), Chromium folks will eventually fix, but I need throttling now. I think your recursion approach has promise. – Dr.YSG Jun 19 '13 at 15:00
3

I agree with eicto: make your own message manager if you can't integrate another one. Here's my crack at a tiny one:

var AjaxQueue = function(max) {
  this.max = max;
  this.requests = [];
  this.current = 0;
}

AjaxQueue.prototype.ajax = function(opts) {
  var queue = this;
  opts.complete = function(jqXHR, textStatus) {
    queue.current -= 1;
    queue.send();
  };
  this.requests.push(opts);
  this.send();
}

AjaxQueue.prototype.send = function(opts) {
  while (this.current < this.max && this.requests.length > 0) {
    $.ajax(this.requests.unshift());
    this.current += 1;
  }
}

I haven't tried using it yet, so there are bound to be bugs. Also it assumes you aren't using a complete option. It just overrides it. If you are you could check for it and make sure the previous complete function(s) still get called.

Benjamin Atkin
  • 14,071
  • 7
  • 61
  • 60
  • Ah great this is perfect. I'll play around with it and see what I can do. My only concern is, is there a potential for a race condition in this.current? I know it's rare that 2 ajax calls complete at the same time, so is it worth thinking about? – stinkycheeseman Aug 08 '11 at 18:52
  • 2
    No. The browser makes sure that two user-defined functions can't be run at the same time. This is why running an infinite while loop will lock up interaction with the web page. It also makes things simpler. – Benjamin Atkin Aug 08 '11 at 18:56
  • Any chance of a usage example? – PlanetWilson Oct 09 '12 at 14:54
  • @Ben I am naive at javascript. can you explain how to use this function/object – Muhammad Adeel Zahid Feb 20 '13 at 19:31
0

The Recursive batching of calls works for me. But since I am getting 4K of XHR2 blobs and saving each one in the IndexedDB (PouchDB). I have threads for both the XHR2 and the IDB puts. So I had to be a bit more sophisticated:

     for (var i in info.LayerInfo) {
        var imageType = (info.LayerInfo[i].Class == "BASE") ? "jpg" : "png";
        info.LayerInfo[i].SaveCount = 0;
        getLayer(0, info, info.LayerInfo[i], info.LayerInfo[i].Path, imageType);
    }
}

function getLayer(index, info, layer, base, imageType) {
    if (layer.Files.length == 0) {
        console.log("Thread done: " + index + " SaveCount: " + layer.SaveCount);
        return;
    }
    var val = layer.Files.shift();
    var path = base + "/" + val.id + "." + imageType;
    $xhr.ajax({
        url: path,
        dataType: "blob",
        success: function (data) {
            console.log("fetched: ", layer.Type + "-" + val.id);
            saveBlob(data, val.size, val.id, layer.Type, index, info, layer, base, imageType);
            if (index < maxThreads - 1) {
                getLayer(++index, info, layer, base, imageType);
            } else {
                return;
            }
        }
    });
}

function saveBlob(blob, length, id, layerID, index, info, layer, base, imageType) {
    if (blob.size != length) {
        console.error("Blob Length found: ", blob.size, " expected: ", length);
    }
    var blobID = layerID + "-" + id;
    var type = blob.type;
    DB.putAttachment(blobID + "/pic", blob, type, function (err, response) {
        if (err) {
            console.error("Could store blob: error: " + err.error + " reason: " + err.reason + " status: " + err.status);
        } else {
            console.log("saved: ", response.id + " rev: " + response.rev);
            layer.SaveCount++;
            getLayer(index, info, layer, base, imageType);
        }
    });
}
Dr.YSG
  • 7,171
  • 22
  • 81
  • 139