Me gustaría actualizar una página basada en los resultados de múltiples solicitudes ajax / json. Usando jQuery, puedo "encadenar" las devoluciones de llamada, como este ejemplo muy simple y despojado:

$.getJSON("/values/1", function(data) {
  // data = {value: 1}
  var value_1 = data.value;

  $.getJSON("/values/2", function(data) {
    // data = {value: 42}
    var value_2 = data.value;

    var sum = value_1 + value_2;

    $('#mynode').html(sum);
  });

});

Sin embargo, esto hace que las solicitudes se realicen en serie. Prefiero una forma de hacer las solicitudes en paralelo y realizar la actualización de la página después de que todas estén completas. ¿Hay alguna forma de hacer esto?

76
Paul 30 jun. 2009 a las 01:03

13 respuestas

La mejor respuesta

Pruebe esta solución, que puede admitir cualquier número específico de consultas paralelas:

var done = 4; // number of total requests
var sum = 0;

/* Normal loops don't create a new scope */
$([1,2,3,4,5]).each(function() {
  var number = this;
  $.getJSON("/values/" + number, function(data) {
    sum += data.value;
    done -= 1;
    if(done == 0) $("#mynode").html(sum);
  });
});
106
Yehuda Katz 29 jun. 2009 a las 21:10

Aquí hay una implementación usando mbostock / queue:

queue()
  .defer(function(callback) {
    $.post('/echo/json/', {json: JSON.stringify({value: 1}), delay: 1}, function(data) {
      callback(null, data.value);
    });
  })
  .defer(function(callback) {
    $.post('/echo/json/', {json: JSON.stringify({value: 3}), delay: 2}, function(data) {
      callback(null, data.value);
    });
  })
  .awaitAll(function(err, results) {
    var result = results.reduce(function(acc, value) {
      return acc + value;
    }, 0);
    console.log(result);
  });

El violín asociado: http://jsfiddle.net/MdbW2/

3
Giovanni Cappellotto 28 ago. 2013 a las 13:28

Podrías hacer algo como esto

var allData = []
$.getJSON("/values/1", function(data) {
    allData.push(data);
    if(data.length == 2){
      processData(allData) // where process data processes all the data
    }
});

$.getJSON("/values/2", function(data) {
    allData.push(data);
    if(data.length == 2){
        processData(allData) // where process data processes all the data
    }
});

var processData = function(data){
     var sum = data[0] + data[1]
     $('#mynode').html(sum);
}
5
agilefall 29 jun. 2009 a las 21:12

Aquí está mi intento de abordar directamente su pregunta

Básicamente, simplemente se acumula y se apila la pila AJAX, se ejecutan todos, y se llama a una función proporcionada al completar todos los eventos: el argumento proporcionado es una matriz de los resultados de todas las solicitudes ajax proporcionadas.

Claramente, este es un código temprano: podría ser más elaborado con esto en términos de flexibilidad.

<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script>
<script type="text/javascript">

var ParallelAjaxExecuter = function( onComplete )
{
  this.requests = [];
  this.results = [];
  this.onComplete = onComplete; 
}

ParallelAjaxExecuter.prototype.addRequest = function( method, url, data, format )
{
  this.requests.push( {
      "method"    : method
    , "url"       : url
    , "data"      : data
    , "format"    : format
    , "completed" : false
  } )
}

ParallelAjaxExecuter.prototype.dispatchAll = function()
{
  var self = this;
  $.each( self.requests, function( i, request )
    {
    request.method( request.url, request.data, function( r )
    {
      return function( data )
      {
        console.log
        r.completed = true;
        self.results.push( data );
        self.checkAndComplete();
      }
    }( request ) )
  } )
}

ParallelAjaxExecuter.prototype.allRequestsCompleted = function()
{
  var i = 0;
  while ( request = this.requests[i++] )
  {
    if ( request.completed === false )
    {
      return false;
    }
  }
  return true;
},

ParallelAjaxExecuter.prototype.checkAndComplete = function()
{
  if ( this.allRequestsCompleted() )
  {
    this.onComplete( this.results );
  }
}

var pe = new ParallelAjaxExecuter( function( results )
{
  alert( eval( results.join( '+' ) ) );
} );

pe.addRequest( $.get, 'test.php', {n:1}, 'text' );
pe.addRequest( $.get, 'test.php', {n:2}, 'text' );
pe.addRequest( $.get, 'test.php', {n:3}, 'text' );
pe.addRequest( $.get, 'test.php', {n:4}, 'text' );

pe.dispatchAll();

</script>

Aquí está test.php

<?php

echo pow( $_GET['n'], 2 );

?>
9
Peter Bailey 29 jun. 2009 a las 21:58

Si el resultado de una solicitud depende de la otra, no puede hacerlas paralelas.

1
Luca Matteis 29 jun. 2009 a las 21:06

Con la siguiente extensión de JQuery (puede escribirse como una función independiente, puede hacer esto:

$.whenAll({
    val1: $.getJSON('/values/1'),
    val2: $.getJSON('/values/2')
})
    .done(function (results) {
        var sum = results.val1.value + results.val2.value;

        $('#mynode').html(sum);
    });

La extensión JQuery (1.x) whenAll ():

$.whenAll = function (deferreds) {
    function isPromise(fn) {
        return fn && typeof fn.then === 'function' &&
            String($.Deferred().then) === String(fn.then);
    }
    var d = $.Deferred(),
        keys = Object.keys(deferreds),
        args = keys.map(function (k) {
            return $.Deferred(function (d) {
                var fn = deferreds[k];

                (isPromise(fn) ? fn : $.Deferred(fn))
                    .done(d.resolve)
                    .fail(function (err) { d.reject(err, k); })
                ;
            });
        });

    $.when.apply(this, args)
        .done(function () {
            var resObj = {},
                resArgs = Array.prototype.slice.call(arguments);
            resArgs.forEach(function (v, i) { resObj[keys[i]] = v; });
            d.resolve(resObj);
        })
        .fail(d.reject);

    return d;
};

Ver ejemplo de jsbin: http://jsbin.com/nuxuciwabu/edit?js,console

3
mraxus 26 ago. 2015 a las 00:20

La solución más profesional para mí sería usar async.js y Array.reduce así:

        async.map([1, 2, 3, 4, 5], function (number, callback) {
            $.getJSON("/values/" + number, function (data) {
                callback(null, data.value);
            });
        }, function (err, results) {
            $("#mynode").html(results.reduce(function(previousValue, currentValue) {
                return previousValue + currentValue;
            }));
        });
3
George Mavritsakis 4 feb. 2016 a las 18:26

ACTUALIZACIÓN Y otros dos años después, esto parece una locura porque la respuesta aceptada ha cambiado a algo mucho mejor. (Aunque todavía no es tan bueno como la respuesta de Yair Leviel usando when de jQuery)

18 meses después, acabo de golpear algo similar. Tengo un botón de actualización y quiero que el contenido anterior sea fadeOut y luego el nuevo contenido en fadeIn. Pero también necesito get el nuevo contenido. El fadeOut y el get son asíncronos, pero sería una pérdida de tiempo ejecutarlos en serie.

Lo que hago es realmente lo mismo que la respuesta aceptada, excepto en la forma de una función reutilizable. Su virtud principal es que es mucho más corta que las otras sugerencias aquí.

var parallel = function(actions, finished) {

  finishedCount = 0;
  var results = [];

  $.each(actions, function(i, action) {

    action(function(result) {

      results[i] = result;
      finishedCount++;

      if (finishedCount == actions.length) {
        finished(results);
      }
    });
  });
};

Le pasa una serie de funciones para ejecutar en paralelo. Cada función debe aceptar otra función a la que pasa su resultado (si lo hay). parallel proporcionará esa función.

También le pasa una función que se llamará cuando se hayan completado todas las operaciones. Esto recibirá una matriz con todos los resultados. Así que mi ejemplo fue:

refreshButton.click(function() {

  parallel([
       function(f) { 
         contentDiv.fadeOut(f); 
       },
       function(f) { 
         portlet.content(f); 
       },
     ], 
     function(results) {
      contentDiv.children().remove();
      contentDiv.append(results[1]);
      contentDiv.fadeIn();
  });
});

Entonces, cuando hago clic en el botón Actualizar, lanzo el efecto fadeOut de jQuery y también mi propia función portlet.content (que hace una sincronización get, crea un nuevo contenido y lo pasa), y luego, cuando ambos están completos, elimino el contenido anterior, agrego el resultado de la segunda función (que está en results[1]) y fadeIn el nuevo contenido.

Como fadeOut no pasa nada a su función de finalización, results[0] presumiblemente contiene undefined, así que lo ignoro. Pero si tuviera tres operaciones con resultados útiles, cada una se ubicaría en la matriz results, en el mismo orden en que pasó las funciones.

7
Daniel Earwicker 3 ago. 2012 a las 09:10

Ejecutar múltiples solicitudes AJAX en paralelo

Al trabajar con API, a veces necesita emitir múltiples solicitudes AJAX a diferentes puntos finales. En lugar de esperar a que se complete una solicitud antes de emitir la siguiente, puede acelerar las cosas con jQuery solicitando los datos en paralelo, utilizando la función $.when() de jQuery:

JS

$.when($.get('1.json'), $.get('2.json')).then(function(r1, r2){
   console.log(r1[0].message + " " + r2[0].message);
});

La función de devolución de llamada se ejecuta cuando ambas solicitudes GET finalizan correctamente. $.when() toma las promesas devueltas por dos $.get() llamadas y construye un nuevo objeto de promesa. Los argumentos r1 y r2 de la devolución de llamada son matrices, cuyos primeros elementos contienen las respuestas del servidor.

8
Jee 26 feb. 2020 a las 11:58

Supongamos que tiene una matriz de nombre de archivo.

var templateNameArray=["test.html","test2.html","test3.html"];

htmlTemplatesLoadStateMap={};
var deffereds=[];
  for (var i = 0; i < templateNameArray.length; i++)
       {
        if (!htmlTemplatesLoadStateMap[templateNameArray[i]]) 
            {         
              deferreds.push($.get("./Content/templates/" +templateNameArray[i], 

                  function (response, status, xhr) {
                      if (status == "error") { } 
                        else {
                                $("body").append(response);
                               }
                         }));             
htmlTemplatesLoadStateMap[templateNameArray[i]] = true;
                       }
                  }
                                      $.when.all(deferreds).always(function(resultsArray) {   yourfunctionTobeExecuted(yourPayload);
                                });
0
Pang 5 sep. 2018 a las 08:16

JQuery $ .when () y $. done () son exactamente lo que necesitas:

$.when($.ajax("/page1.php"), $.ajax("/page2.php"))
  .then(myFunc, myFailure);
115
Rob Hruska 8 may. 2012 a las 16:47

Sobre la base de la respuesta de Yair. Puede definir las promesas ajax dinámicamente.

var start = 1; // starting value
var len = 2; // no. of requests

var promises = (new Array(len)).fill().map(function() {
    return $.ajax("/values/" + i++);
});

$.when.apply($, promises)
  .then(myFunc, myFailure);
1
Gabiriele Lalasava 21 ene. 2017 a las 15:18

Actualización: Según la respuesta dada por Yair Leviel, esta respuesta es obsoleta. Use una biblioteca de promesas, como jQuery.when () o Q.js.


Creé una solución de propósito general como una extensión jQuery. Podría usar algunos ajustes para hacerlo más general, pero se adaptaba a mis necesidades. La ventaja de esta técnica sobre las otras en esta publicación al momento de escribir esto fue que se puede usar cualquier tipo de procesamiento asincrónico con una devolución de llamada.

Nota: usaría extensiones Rx para JavaScript en lugar de esto si pensara que mi cliente estaría de acuerdo con depender de otra biblioteca de terceros :)

// jQuery extension for running multiple async methods in parallel
// and getting a callback with all results when all of them have completed.
//
// Each worker is a function that takes a callback as its only argument, and
// fires up an async process that calls this callback with its result.
//
// Example:
//      $.parallel(
//          function (callback) { $.get("form.htm", {}, callback, "html"); },
//          function (callback) { $.post("data.aspx", {}, callback, "json"); },
//          function (formHtml, dataJson) { 
//              // Handle success; each argument to this function is 
//              // the result of correlating ajax call above.
//          }
//      );

(function ($) {

    $.parallel = function (anyNumberOfWorkers, allDoneCallback) {

    var workers = [];
    var workersCompleteCallback = null;

    // To support any number of workers, use "arguments" variable to
    // access function arguments rather than the names above.
    var lastArgIndex = arguments.length - 1;
    $.each(arguments, function (index) {
        if (index == lastArgIndex) {
            workersCompleteCallback = this;
        } else {
            workers.push({ fn: this, done: false, result: null });
        }
    });

    // Short circuit this edge case
    if (workers.length == 0) {
        workersCompleteCallback();
        return;
    }

    // Fire off each worker process, asking it to report back to onWorkerDone.
    $.each(workers, function (workerIndex) {
        var worker = this;
        var callback = function () { onWorkerDone(worker, arguments); };
        worker.fn(callback);
    });

    // Store results and update status as each item completes.
    // The [0] on workerResultS below assumes the client only needs the first parameter
    // passed into the return callback. This simplifies the handling in allDoneCallback,
    // but may need to be removed if you need access to all parameters of the result.
    // For example, $.post calls back with success(data, textStatus, XMLHttpRequest).  If
    // you need textStatus or XMLHttpRequest then pull off the [0] below.
    function onWorkerDone(worker, workerResult) {
        worker.done = true;
        worker.result = workerResult[0]; // this is the [0] ref'd above.
        var allResults = [];
        for (var i = 0; i < workers.length; i++) {
            if (!workers[i].done) return;
            else allResults.push(workers[i].result);
        }
        workersCompleteCallback.apply(this, allResults);
    }
};

})(jQuery);
9
pettys 28 may. 2013 a las 14:07