Búsqueda de sitios web

javascript - ¿Cómo hacer un bucle asíncrono limpio?


Siguiendo los estándares típicos de REST, dividí mis recursos en endpoints y llamadas separadas. Los dos objetos principales en cuestión aquí son List y Item (y, por supuesto, una lista tiene una lista de elementos, además de algunos otros datos asociados con ella).

Entonces, si un usuario quiere recuperar sus listas, podría hacer una solicitud Get a api/Lists

Luego el usuario podría querer obtener los elementos de una de esas listas y hacer un Get a api/ListItems/4 donde 4 se encontró a partir de List.listId recuperado en la llamada anterior.

Todo esto está muy bien: el atributo options.complete de $.ajax me permite apuntar a un método de retorno de llamada, así que puedo racionalizar estos dos eventos.

Pero las cosas se complican mucho si quiero obtener los elementos de todas las listas en cuestión. Por ejemplo, supongamos que tengo una función de biblioteca llamada makeGetRequest que recibe el endpoint y una función de devolución de llamada, para hacer que este código sea más limpio. Simplemente obtener 3 elementos de la manera ingenua da como resultado esto:

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

¡Repugnante! Este es el tipo de cosa por la que en programación 101 nos dan un golpe en la muñeca y nos señalan los bucles. Pero, ¿cómo puedes hacer esto con un bucle, sin tener que depender de almacenamiento externo?

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!
}

E incluso con almacenamiento, tendría que saber cuándo todos han terminado y han recuperado sus datos, y llamar a alguna función maestra que recupere todos los datos recolectados y haga algo con ellos.

¿Existe una práctica para manejar esto? Esto debe haberse resuelto muchas veces antes...

Respuesta #1

Intenta usar una pila de parámetros de endpoint:

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"});

Entonces puedes hacerlo recursivo en tu manejador de éxito:

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)
}

Y puedes iniciarlo con una sola llamada así:

getResources(params.shift().endpoint);

Editar:

Para mantener todo autocontenido y fuera del ámbito global, puedes usar una función y proporcionar un 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);

}

En uso:

downloadResources(function(data) {
   // Do stuff with your data set
});

Respuesta #2

La respuesta de dmck probablemente sea tu mejor opción. Sin embargo, otra opción es hacer una opción de lista masiva, para que tu API soporte solicitudes como api/ListItems/?id=1&id=2&id=3.

También podrías hacer un endpoint de búsqueda de API, si eso se ajusta más a tu estética personal.