Übergeben Sie ein Array von Zurückgestellten an $ .when ()


447

Hier ist ein erfundenes Beispiel dafür, was los ist: http://jsfiddle.net/adamjford/YNGcm/20/

HTML:

<a href="#">Click me!</a>
<div></div>

JavaScript:

function getSomeDeferredStuff() {
    var deferreds = [];

    var i = 1;
    for (i = 1; i <= 10; i++) {
        var count = i;

        deferreds.push(
        $.post('/echo/html/', {
            html: "<p>Task #" + count + " complete.",
            delay: count
        }).success(function(data) {
            $("div").append(data);
        }));
    }

    return deferreds;
}

$(function() {
    $("a").click(function() {
        var deferreds = getSomeDeferredStuff();

        $.when(deferreds).done(function() {
            $("div").append("<p>All done!</p>");
        });
    });
});

Ich möchte "Alles erledigt!" Wird angezeigt, nachdem alle zurückgestellten Aufgaben abgeschlossen wurden, $.when()scheint jedoch nicht zu wissen, wie mit einem Array von zurückgestellten Objekten umgegangen werden soll. "Alles erledigt!" geschieht zuerst, weil das Array kein verzögertes Objekt ist, also geht jQuery vor und geht davon aus, dass es gerade fertig ist.

Ich weiß, dass man die Objekte wie $.when(deferred1, deferred2, ..., deferredX)folgt an die Funktion übergeben könnte, aber es ist nicht bekannt, wie viele verzögerte Objekte in dem tatsächlichen Problem, das ich zu lösen versuche, ausgeführt werden.



Eine neue, einfachere Antwort auf diese sehr alte Frage wurde hinzugefügt. Sie müssen kein Array oder $.when.applyüberhaupt kein Array verwenden , um das gleiche Ergebnis zu erzielen.
Gone Coding

Rollback Frage Thema, da es zu spezifisch war (dies ist nicht nur ein AJAX-Problem)
Alnitak

Antworten:


732

Verwenden Sie Folgendes, um ein Array von Werten an eine Funktion zu übergeben, die normalerweise erwartet, dass es sich um separate Parameter handelt. Function.prototype.applyIn diesem Fall benötigen Sie also:

$.when.apply($, my_array).then( ___ );

Siehe http://jsfiddle.net/YNGcm/21/

In ES6 können Sie stattdessen den ... Spread-Operator verwenden:

$.when(...my_array).then( ___ );

In beiden Fällen .thenmüsste dieser Handler das argumentsArray verarbeiten, um das Ergebnis jedes Versprechens abzurufen , da es unwahrscheinlich ist, dass Sie im Voraus wissen, wie viele formale Parameter der Handler benötigt .


4
Das funktioniert, großartig. :) Ich bin erstaunt, dass ich eine so einfache Änderung über Google nicht ausgraben konnte!
Adamjford

9
Das ist , weil es sich um eine generische Methode ist, nicht spezifisch auf $.when- f.apply(ctx, my_array)rufen fmit this == ctxund die auf die vorgetragenen Argumente Inhalt von my_array.
Alnitak

4
@Alnitak: Es ist mir ein wenig peinlich, dass ich nichts über diese Methode wusste, wenn man bedenkt, wie lange ich jetzt JavaScript schreibe!
Adamjford

5
FWIW, der Link in Elis Antwort auf eine frühere Frage mit der Diskussion über das Übergeben von $vs nullals erstem Parameter ist eine Lektüre wert. In diesem speziellen Fall spielt es jedoch keine Rolle.
Alnitak

4
@Alnitak: Ja, aber $weniger tippend als nullund Sie sind sicher, wenn sich die $.whenImplementierung ändert (nicht, dass dies in diesem Fall wahrscheinlich ist, aber warum nicht thisstandardmäßig unverändert bleiben ).
Tomasz Zieliński

109

Die obigen Problemumgehungen (danke!) Beheben das Problem, dass die für die verzögerte resolve()Methode bereitgestellten Objekte wiederhergestellt werden, nicht richtig , da jQuery die done()und fail()Rückrufe mit einzelnen Parametern aufruft , nicht mit einem Array. Das heißt, wir müssen das argumentsPseudo-Array verwenden, um alle aufgelösten / zurückgewiesenen Objekte zu erhalten, die vom Array der zurückgestellten zurückgesendet werden, was hässlich ist:

$.when.apply($,deferreds).then(function() {
     var objects=arguments; // The array of resolved objects as a pseudo-array
     ...
};

Da wir eine Reihe von Verzögerungen übergeben haben, wäre es schön, eine Reihe von Ergebnissen zurückzugewinnen. Es wäre auch schön, ein tatsächliches Array anstelle eines Pseudo-Arrays zurückzugewinnen, damit wir Methoden wie verwenden können Array.sort().

Hier ist eine Lösung, die von der Methode von when.js inspiriert ist when.all(), mit der diese Probleme behoben werden:

// Put somewhere in your scripting environment
if (typeof jQuery.when.all === 'undefined') {
    jQuery.when.all = function (deferreds) {
        return $.Deferred(function (def) {
            $.when.apply(jQuery, deferreds).then(
                function () {
                    def.resolveWith(this, [Array.prototype.slice.call(arguments)]);
                },
                function () {
                    def.rejectWith(this, [Array.prototype.slice.call(arguments)]);
                });
        });
    }
}

Jetzt können Sie einfach eine Reihe von zurückgestellten / versprochenen Versprechungen übergeben und eine Reihe von aufgelösten / abgelehnten Objekten in Ihrem Rückruf zurückerhalten, wie folgt:

$.when.all(deferreds).then(function(objects) {
    console.log("Resolved objects:", objects);
});

6
Möglicherweise möchten Sie auflösen mit und ablehnen mit verwenden, nur damit Sie die gleichen ursprünglichen Verzögerungen erhalten wie 'this' deferred.resolveWith (this, [Array.prototype.slice.call (Argumente)]) usw.
Jamie Pate

1
Es gibt nur ein kleines Problem mit Ihrem Code: Wenn das Array nur ein Element enthält, gibt das Ergebnis-Array nur dieses Ergebnis zurück, anstatt eines Arrays mit einem einzelnen Element (wodurch der Code, der ein Array erwartet, beschädigt wird). Verwenden Sie diese Funktion var toArray = function (args) { return deferreds.length > 1 ? $.makeArray(args) : [args]; }anstelle von , um das Problem zu beheben Array.prototype.slice.call.
Luan Nico

Hm, das scheint keine 404 zu fangen.
t.mikael.d

Fand den Grund, .fail sollte stattdessen .reject sein - damit es 404's fangen kann.
t.mikael.d

38

Sie können die whenMethode auf Ihr Array anwenden :

var arr = [ /* Deferred objects */ ];

$.when.apply($, arr);

Wie arbeiten Sie mit einem Array von jQuery Deferreds?


Ich habe diese Frage tatsächlich gesehen, aber ich denke, all die zusätzlichen Details in dieser Frage haben dazu geführt, dass die Antwort auf mein Problem (das genau dort drin war) direkt über meinen Kopf flog.
Adamjford

1
@adamjford, wenn Sie sich dadurch besser fühlen, fand ich Ihre Frage leichter zu konsumieren (und zuerst bei meiner speziellen Google-Suche nach genau diesem Problem).
Patridge

@patridge: Freut mich zu hören, dass es dir geholfen hat!
Adamjford

Dies ist eine großartige Antwort, aber mir war nicht klar, wie dies auf das Beispiel in der ursprünglichen Frage zutrifft. Nach Durchsicht der verknüpften Frage wurde klar, dass die Zeile "$ .when (verzögert) .done (function () {" einfach in "$ .when.apply ($, verzögert) .done (function () {geändert werden sollte ". Richtig?
Garland Pope

7

Wenn Sie mehrere parallele AJAX-Anrufe aufrufen, haben Sie zwei Möglichkeiten, die jeweiligen Antworten zu verarbeiten.

  1. Verwenden Sie den synchronen AJAX-Aufruf / nacheinander / nicht empfohlen
  2. Verwenden Sie ein Promises'Array, $.whendas promises akzeptiert, und sein Rückruf .donewird aufgerufen, wenn alle promises mit den entsprechenden Antworten erfolgreich zurückgegeben wurden.

Beispiel

function ajaxRequest(capitalCity) {
   return $.ajax({
        url: 'https://restcountries.eu/rest/v1/capital/'+capitalCity,
        success: function(response) {
        },
        error: function(response) {
          console.log("Error")
        }
    });
}
$(function(){
   var capitalCities = ['Delhi', 'Beijing', 'Washington', 'Tokyo', 'London'];
   $('#capitals').text(capitalCities);

   function getCountryCapitals(){ //do multiple parallel ajax requests
      var promises = [];   
      for(var i=0,l=capitalCities.length; i<l; i++){
            var promise = ajaxRequest(capitalCities[i]);
            promises.push(promise);
      }
  
      $.when.apply($, promises)
        .done(fillCountryCapitals);
   }
  
   function fillCountryCapitals(){
        var countries = [];
        var responses = arguments;
        for(i in responses){
            console.dir(responses[i]);
            countries.push(responses[i][0][0].nativeName)
        }  
        $('#countries').text(countries);
   }
  
   getCountryCapitals()
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
  <h4>Capital Cities : </h4> <span id="capitals"></span>
  <h4>Respective Country's Native Names : </h4> <span id="countries"></span>
</div>


1
Ihre Antwort reicht zu weit, ebenso wie Ihre Bearbeitung des Fragentitels. Das OP wusste bereits, wie man AJAX-Aufrufe tätigt und eine Reihe von zurückgestellten Objekten erhält. Der einzige Punkt der Frage war, wie dieses Array übergeben werden soll $.when.
Alnitak

5
Ich dachte, es wäre besser, mit den verfügbaren Optionen im Detail zu erklären, und dafür halte ich keine Abstimmung für notwendig.
Vinayakj

2
Die Abwertung war für 1. sogar das Vorschlagen einer Synchronisierung (wenn auch mit der Empfehlung, dies nicht zu tun) 2. den Code mit schlechter Qualität in den Beispielen (einschließlich for ... ineines Arrays?!)
Alnitak

1
1. Einverstanden, sollte gehabt haben (not recommended)2.Nicht zustimmen - for ... inist in Ordnung, da das Array nur die Eigenschaften enthält, die benötigt werden (keine zusätzlichen Eigenschaften). Danke trotzdem
vinayakj

1
Betreff: 2 - Das Problem ist, dass es möglicherweise von anderen Personen kopiert wird, die diese Garantie nicht übernehmen können oder dumm genug waren, etwas hinzuzufügen Array.prototype. In jedem Fall ist es für nicht leistungskritischen Code besser, .mapanstelle einer for/ push-Schleife zu verwenden, z var promises = capitalCities.map(ajaxRequest); $.when.apply($, promises).then(fillCountryCapitals). B. - Job erledigt.
Alnitak

6

Als einfache Alternative, die nicht benötigt $.when.applyoder arraykönnen Sie das folgende Muster verwenden ein einziges Versprechen für mehr parallelen Versprechen zu generieren:

promise = $.when(promise, anotherPromise);

z.B

function GetSomeDeferredStuff() {
    // Start with an empty resolved promise (or undefined does the same!)
    var promise;
    var i = 1;
    for (i = 1; i <= 5; i++) {
        var count = i;

        promise = $.when(promise,
        $.ajax({
            type: "POST",
            url: '/echo/html/',
            data: {
                html: "<p>Task #" + count + " complete.",
                delay: count / 2
            },
            success: function (data) {
                $("div").append(data);
            }
        }));
    }
    return promise;
}

$(function () {
    $("a").click(function () {
        var promise = GetSomeDeferredStuff();
        promise.then(function () {
            $("div").append("<p>All done!</p>");
        });
    });
});

Anmerkungen:

  • Ich habe das herausgefunden, nachdem ich gesehen hatte, wie jemand nacheinander Kettenversprechen gemacht hat promise = promise.then(newpromise)
  • Der Nachteil ist, dass hinter den Kulissen zusätzliche Versprechungsobjekte erstellt werden und alle am Ende übergebenen Parameter nicht sehr nützlich sind (da sie in zusätzlichen Objekten verschachtelt sind). Für das, was Sie wollen, ist es kurz und einfach.
  • Der Vorteil ist, dass kein Array oder Array-Management erforderlich ist.

2
Korrigieren Sie mich, wenn ich falsch liege, aber Ihr Ansatz verschachtelt effektiv $ .when ($ .when ($ .when (...))), sodass Sie bei 10 Iterationen rekursiv 10 Ebenen tief verschachtelt sind. Dies scheint nicht sehr parallel zu sein, da Sie warten müssen, bis jede Ebene das verschachtelte Versprechen eines Kindes zurückgibt, bevor es sein eigenes Versprechen zurückgeben kann. Ich denke, der Array-Ansatz in der akzeptierten Antwort ist viel sauberer, da er das eingebaute flexible Parameterverhalten verwendet die $ .when () Methode.
Anthony McLin

@AnthonyMcLin: Dies soll eine einfachere Alternative zur Codierung bieten, nicht eine bessere Leistung (die bei den meisten Async-Codierungen vernachlässigbar ist), wie dies bei der Verkettung von then()Aufrufen auf ähnliche Weise der Fall ist. Das Verhalten bei $.whenist, so zu handeln, wie es parallel ist (nicht verkettet). Bitte versuchen Sie es, bevor Sie eine nützliche Alternative wegwerfen, da es funktioniert :)
Gone Coding

2
@Alnitak: Pferde für Kurse. Sie haben sicherlich ein Recht auf eine Meinung, aber Sie haben diese offensichtlich nicht selbst verwendet. Meine eigene Meinung basiert auf praktischen Anwendungen dieser Technik. Es funktioniert und hat Verwendungszwecke. Warum also ein Tool aus der Toolbox werfen, das auf Übertreibungen wie "jede Menge Vorbehalte" (eins) und "nichts löst" (nicht wahr - es eliminiert die Array-Verarbeitung und vereinfacht die Verkettung paralleler Versprechen, wo die Rückkehr erfolgt) Werte werden nicht benötigt, die, wie Sie wissen sollten, in Fällen paralleler Verarbeitung ohnehin selten verwendet werden. Downvotes sollen für "diese Antwort ist nicht nützlich" sein :)
Gone Coding

1
Hallo @GoneCoding. Darf ich Sie bitten, Ihren Antworten keinen Abstimmungskommentar hinzuzufügen? Das ist für Kommentare geeignet, aber ansonsten lenkt das Rauschen vom ansonsten guten Inhalt ab. Vielen Dank.
Halfer

1
@halfer: Ich poste nicht mehr, aber ich ärgere mich über die Unwissenheit, die irgendetwas Originellem entgegengebracht wird. Ich behalte heutzutage alle neuen Ideen für mich :)
Gone Coding

4

Ich möchte einen anderen mit $ .each vorschlagen:

  1. Wir können die Ajax-Funktion wie folgt deklarieren:

    function ajaxFn(someData) {
        this.someData = someData;
        var that = this;
        return function () {
            var promise = $.Deferred();
            $.ajax({
                method: "POST",
                url: "url",
                data: that.someData,
                success: function(data) {
                    promise.resolve(data);
                },
                error: function(data) {
                    promise.reject(data);
                }
            })
            return promise;
        }
    }
  2. Teil des Codes, in dem wir ein Array von Funktionen mit Ajax zum Senden erstellen:

    var arrayOfFn = [];
    for (var i = 0; i < someDataArray.length; i++) {
        var ajaxFnForArray = new ajaxFn(someDataArray[i]);
        arrayOfFn.push(ajaxFnForArray);
    }
  3. Und Funktionen mit dem Senden von Ajax aufrufen:

    $.when(
        $.each(arrayOfFn, function(index, value) {
            value.call()
        })
    ).then(function() {
            alert("Cheer!");
        }
    )

1

Wenn Sie transpilieren und Zugriff auf ES6 haben, können Sie die Spread-Syntax verwenden, die jedes iterierbare Element eines Objekts als diskretes Argument anwendet, genau so, wie es $.when()benötigt wird.

$.when(...deferreds).done(() => {
    // do stuff
});

MDN Link - Spread-Syntax


0

Wenn Sie angleJS oder eine Variante der Q-Versprechen-Bibliothek verwenden, haben Sie eine .all()Methode, die genau dieses Problem löst.

var savePromises = [];
angular.forEach(models, function(model){
  savePromises.push(
    model.saveToServer()
  )
});

$q.all(savePromises).then(
  function success(results){...},
  function failed(results){...}
);

Siehe die vollständige API:

https://github.com/kriskowal/q/wiki/API-Reference#promiseall

https://docs.angularjs.org/api/ng/service/$q


4
Dies ist völlig irrelevant.
Benjamin Gruenbaum

@BenjaminGruenbaum Wie so? Alle Javascript-Versprechen-Bibliotheken haben eine ähnliche API, und es ist nichts Falsches daran, die verschiedenen Implementierungen anzuzeigen. Ich habe diese Seite erreicht und nach einer Antwort für Angular gesucht. Ich vermute, dass viele andere Benutzer diese Seite erreichen und sich nicht unbedingt in einer Nur-Abfrage-Umgebung befinden.
MastaBlasta

2
Da die Versprechen von jQuery diese API nicht teilen, ist dies als Antwort auf den Stapelüberlauf völlig unangemessen. Es gibt ähnliche Antworten für Angular und Sie können dort nachfragen. (Ganz zu schweigen davon, du solltest .maphier aber na ja).
Benjamin Gruenbaum

0

Ich hatte einen sehr ähnlichen Fall, in dem ich in jeder Schleife postete und dann in einigen Feldern das HTML-Markup aus den vom Ajax empfangenen Zahlen festlegte. Ich musste dann eine Summe der (jetzt aktualisierten) Werte dieser Felder erstellen und in ein Gesamtfeld einfügen.

Daher bestand das Problem darin, dass ich versuchte, eine Summe für alle Nummern zu erstellen, aber noch keine Daten von den asynchronen Ajax-Aufrufen zurückgekommen waren. Ich musste diese Funktionalität in einigen Funktionen vervollständigen, um den Code wiederverwenden zu können. Meine äußere Funktion wartet auf die Daten, bevor ich dann einige Dinge mit dem vollständig aktualisierten DOM mache.

    // 1st
    function Outer() {
        var deferreds = GetAllData();

        $.when.apply($, deferreds).done(function () {
            // now you can do whatever you want with the updated page
        });
    }

    // 2nd
    function GetAllData() {
        var deferreds = [];
        $('.calculatedField').each(function (data) {
            deferreds.push(GetIndividualData($(this)));
        });
        return deferreds;
    }

    // 3rd
    function GetIndividualData(item) {
        var def = new $.Deferred();
        $.post('@Url.Action("GetData")', function (data) {
            item.html(data.valueFromAjax);
            def.resolve(data);
        });
        return def;
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.