Parallele asynchrone Ajax-Anforderungen mit jQuery

76

Ich möchte eine Seite basierend auf den Ergebnissen mehrerer Ajax / JSON-Anfragen aktualisieren. Mit jQuery kann ich die Rückrufe "verketten", wie in diesem sehr einfachen Beispiel:

$.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);
  });

});

Dies führt jedoch dazu, dass die Anforderungen seriell gestellt werden. Ich möchte lieber die Anforderungen parallel stellen und die Seitenaktualisierung durchführen, nachdem alle abgeschlossen sind. Gibt es eine Möglichkeit, dies zu tun?

Paul
quelle

Antworten:

107

Probieren Sie diese Lösung aus, die eine bestimmte Anzahl paralleler Abfragen unterstützen kann:

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);
  });
});
Yehuda Katz
quelle
33
Wenn ich mich nicht irre, sind Sie der Autor von 'jQuery In Action'?
Karim79
1
Tolles Buch! Dein Name hat Alarm in meinem Kopf ausgelöst!
Karim79
2
Ich ging mit etwas ähnlichem wie Ihrem und Agilefall: var results = {}; var Anfragen = 0; var urls = ["Werte / 1", "Werte / 2", "Werte / 3"]; $ .each (URLs, Funktion (URL) {$ .getJSON (URL, Funktion (Daten) {Ergebnisse [URL] = Datenwert; ++ Anfragen; if (Anfragen == 3) {$ ('# mynode') .html (Ergebnisse [URLs [0]] / Ergebnisse [URLs [1]] * Ergebnisse [URLs [2]]);}});});
Paul
Ich habe etwas Ähnliches gemacht. Am Ende habe ich meine Anfrage konsolidiert. Aber es ist gut zu wissen, wie man das für alle Fälle macht. Ich habe einen Fortschrittsbalken angezeigt, der in diesem Fall gut funktioniert, da der Code rückrufgesteuert ist. Verwenden Sie in diesem Fall einfach 100 * ((4-erledigt) / 4) für den Prozentsatz, der erledigt wurde.
Nosredna
1
Sieht aus wie Code in "if" könnte mehrmals ausgeführt werden. Ist "done - = 1" auch atomar?
Gzorg
119

jQuery $ .when () und $ .done () sind genau das, was Sie brauchen:

$.when($.ajax("/page1.php"), $.ajax("/page2.php"))
  .then(myFunc, myFailure);
Yair Leviel
quelle
+1 Ich hatte irgendwo gehört, dass Versprechen nicht gut komponieren ... Anscheinend muss ich das vergessen!
Daniel Earwicker
Nichts für ungut, aber ist diese Antwort der von @ yehuda-katz nicht weit überlegen? (Da dies funktioniert)
Herbert
5
Diese Antwort ist nicht klar, wie auf die Daten zugegriffen werden soll, nachdem die Ajax-Aufrufe abgeschlossen sind. Was wird an myFunc übergeben und wie greife ich auf die Anrufe zu?
Martin Burch
2
Beispiel mit myFunc und myFailure => codepen.io/jacobgoh101/pen/YaJOzx?editors=0010
Jacob Goh
9

Hier ist mein Versuch, Ihre Frage direkt zu beantworten

Grundsätzlich bauen Sie einfach einen AJAX-Aufrufstapel auf, führen sie alle aus und eine bereitgestellte Funktion wird nach Abschluss aller Ereignisse aufgerufen. Das angegebene Argument ist ein Array der Ergebnisse aller bereitgestellten Ajax-Anforderungen.

Dies ist eindeutig ein früher Code - Sie könnten diesbezüglich in Bezug auf die Flexibilität ausführlicher vorgehen.

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

hier ist test.php

<?php

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

?>
Peter Bailey
quelle
18
OO zu weit gegangen.
jmah
9

Update: Laut der Antwort von Yair Leviel ist diese Antwort veraltet. Verwenden Sie eine Versprechensbibliothek wie jQuery.when () oder Q.js.


Ich habe eine Allzwecklösung als jQuery-Erweiterung erstellt. Könnte eine Feinabstimmung gebrauchen, um es allgemeiner zu machen, aber es entsprach meinen Bedürfnissen. Der Vorteil dieser Technik gegenüber den anderen in diesem Beitrag zum Zeitpunkt dieses Schreibens bestand darin, dass jede Art von asynchroner Verarbeitung mit einem Rückruf verwendet werden kann.

Hinweis: Ich würde stattdessen Rx-Erweiterungen für JavaScript verwenden, wenn ich dachte, mein Client wäre damit einverstanden, eine Abhängigkeit von einer weiteren Bibliothek eines Drittanbieters zu übernehmen :)

// 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);
Pettys
quelle
2
Ich habe dies verwendet und es funktioniert großartig! ... aber ich habe eine Verbesserung: Wenn Sie den endgültigen Rückrufaufruf so ändern, dass "apply" verwendet wird, erhalten Sie separate Argumente für Ihren Rückruf anstelle einer einzelnen Liste von Argumenten: dh workerCompleteCallback.apply (this, allResults);
Nick Perkins
9

Führen Sie mehrere AJAX-Anforderungen gleichzeitig aus

Wenn Sie mit APIs arbeiten, müssen Sie manchmal mehrere AJAX-Anforderungen an verschiedene Endpunkte senden. Anstatt auf die Fertigstellung einer Anforderung zu warten, bevor die nächste ausgegeben wird, können Sie die Arbeit mit jQuery beschleunigen, indem Sie die Daten mithilfe der $.when()Funktion von jQuery parallel anfordern :

JS

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

Die Rückruffunktion wird ausgeführt, wenn beide GET-Anforderungen erfolgreich abgeschlossen wurden. $.when()Nimmt die von zwei $.get()Aufrufen zurückgegebenen Versprechen und erstellt ein neues Versprechenobjekt. Die Argumente r1und r2des Rückrufs sind Arrays, deren erste Elemente die Serverantworten enthalten.

sri_bb
quelle
7

UPDATE Und weitere zwei Jahre später sieht das verrückt aus, weil sich die akzeptierte Antwort in etwas viel Besseres geändert hat! (Obwohl immer noch nicht so gut wie Yair Leviels Antwort mit jQuery's when)

18 Monate später habe ich gerade etwas Ähnliches getroffen. Ich habe eine Schaltfläche zum Aktualisieren und möchte, dass der alte Inhalt fadeOutund dann der neue Inhalt angezeigt werden fadeIn. Ich brauche aber auch getden neuen Inhalt. Die fadeOutund die getsind asynchron, aber es wäre Zeitverschwendung, sie seriell auszuführen.

Was ich tue, ist wirklich dasselbe wie die akzeptierte Antwort, außer in Form einer wiederverwendbaren Funktion. Seine Haupttugend ist, dass es viel kürzer ist als die anderen Vorschläge hier.

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

Sie übergeben ihm eine Reihe von Funktionen, die parallel ausgeführt werden sollen. Jede Funktion sollte eine andere Funktion akzeptieren, an die sie ihr Ergebnis weitergibt (falls vorhanden). parallelwird diese Funktion liefern.

Sie übergeben ihm auch eine Funktion, die aufgerufen werden soll, wenn alle Vorgänge abgeschlossen sind. Dies erhält ein Array mit allen Ergebnissen in. Mein Beispiel war also:

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

Wenn Sie also auf die Schaltfläche "Aktualisieren" klicken, starte ich den fadeOutEffekt von jQuery und meine eigene portlet.contentFunktion (die eine Asynchronisierung ausführt get, ein neues Stück Inhalt erstellt und weiterleitet). Wenn beide abgeschlossen sind, entferne ich den alten Inhalt und füge das Ergebnis hinzu der zweiten Funktion (die in ist results[1]) und fadeIndes neuen Inhalts.

Da fadeOutnichts an seine Vervollständigungsfunktion übergeben wird, results[0]vermutlich enthält undefined, ignoriere ich es. Wenn Sie jedoch drei Operationen mit nützlichen Ergebnissen hätten, würden sie jeweils in resultsderselben Reihenfolge in das Array eingesteckt, in der Sie die Funktionen übergeben haben.

Daniel Earwicker
quelle
5

Sie könnten so etwas tun

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);
}
Agilefall
quelle
3

Hier ist eine Implementierung mit 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);
  });

Die zugehörige Geige: http://jsfiddle.net/MdbW2/

Giovanni Cappellotto
quelle
3

Mit der folgenden Erweiterung von JQuery (kann als eigenständige Funktion geschrieben werden) können Sie dies tun:

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

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

Die JQuery (1.x) -Erweiterung 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;
};

Siehe jsbin-Beispiel: http://jsbin.com/nuxuciwabu/edit?js,console

mraxus
quelle
3

Die professionellste Lösung für mich wäre die Verwendung von async.js und Array.reduce wie folgt:

        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;
            }));
        });
George Mavritsakis
quelle
1

Wenn das Ergebnis einer Anforderung von der anderen abhängt, können Sie sie nicht parallel schalten.

Luca Matteis
quelle
1
Eins hängt nicht vom anderen ab, aber das Endergebnis hängt davon ab, ob jedes abgeschlossen ist.
Paul
1
Da es sich um eine einfache mathematische Operation zwischen den Daten handelt, die Sie zurückerhalten haben, können Sie eine Variable außerhalb des Gültigkeitsbereichs verwenden, um die hinzugefügten Daten zu verfolgen. In den meisten Fällen ist dies jedoch keine wertvolle Lösung für parallele Anforderungen, die von den Daten des jeweils anderen abhängen.
Luca Matteis
1

Aufbauend auf Yairs Antwort. Sie können die Ajax-Versprechen dynamisch definieren.

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);
Gabiriele Lalasava
quelle
0

Angenommen, Sie haben ein Array mit Dateinamen.

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);
                                });
Prashant Saurabh
quelle