Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a clean Asynchronous loop?

Following typical REST standards, I broke up my resources into separate endpoints and calls. The main two objects in question here are List and Item (and of course, a list has a list of items, as well as some other data associated with it).

So if a user wants to retrieve his lists, he might make a Get request to api/Lists

Then the user might want to get the items in one of those lists and make a Get to api/ListItems/4 where 4 was found from List.listId retrieved in the previous call.

This is all well and good: the options.complete attribute of $.ajax lets me point to a callback method, so I can streamline these two events.

But things get very messy if I want to get the elements for all the lists in question. For example, let's assume I have a library function called makeGetRequest that takes in the end point and callback function, to make this code cleaner. Simply retrieving 3 elements the naive way results in this:

var success1 = function(elements){
    var success2 = function(elements){
       makeGetRequest("api/ListItems/3", finalSuccess);
    }
    makeGetRequest("api/ListItems/2", success2);
}
makeGetRequest("api/ListItems/1", success1);

Disgusting! This is the kind of thing in programming 101 we're smacked across the wrists for and pointed to loops. But how can you do this with a loop, without having to rely on external storage?

for(var i : values){
    makeGetRequest("api/ListItems/" + i, successFunction);
}

function successFunction(items){
    //I am called i-many times, each time only having ONE list's worth of items!
}

And even with storage, I would have to know when all have finished and retrieved their data, and call some master function that retrieves all the collected data and does something with it.

Is there a practice for handling this? This must have been solved many times before...

like image 406
Nick Avatar asked Aug 02 '12 17:08

Nick


2 Answers

Try using a stack of endpoint parameters:

var params = [];
var results [];

params.push({endpoint: "api/ListItems/1"});
params.push({endpoint: "api/ListItems/2"});
params.push({endpoint: "api/ListItems/3"});
params.push({endpoint: "api/ListItems/4"});

Then you can make it recursive in your success handler:

function getResources(endPoint) {

   var options = {} // Ajax Options
   options.success = function (data) {
       if (params.length > 0) {
           results.push({endpoint: endpoint, data: data});
           getResources(params.shift().endpoint);
       }
       else {
          theMasterFunction(results)
       }
   }

   $.get(endPoint, options)
}

And you can start it with a single call like this:

getResources(params.shift().endpoint);

Edit:

To keep everything self contained and out of global scope you can use a function and provide a callback:

function downloadResources(callback) {

    var endpoints = [];
    var results [];

    endpoints.push({endpoint: "api/ListItems/1"});
    endpoints.push({endpoint: "api/ListItems/2"});
    endpoints.push({endpoint: "api/ListItems/3"});
    endpoints.push({endpoint: "api/ListItems/4"});

    function getResources(endPoint) {
        var options = {} // Ajax Options
        options.success = function (data) {
           if (endpoints.length > 0) {
               results.push({endpoint: endpoint, data: data});
               getResources(endpoints.shift().endpoint);
           }
           else {
              callback(results)
           }
        }
       $.get(endPoint, options)
    }

    getResources(endpoints.shift().endpoint);

}

In use:

downloadResources(function(data) {
   // Do stuff with your data set
});
like image 81
dmck Avatar answered Sep 26 '22 07:09

dmck


dmck's answer is probably your best bet. However, another option is to do a bulk list option, so that your api supports requests like api/ListItems/?id=1&id=2&id=3.

You could also do an api search endpoint, if that fits your personal aesthetic more.

like image 41
Jordan Avatar answered Sep 24 '22 07:09

Jordan