jQuery verschiebt und verspricht - .then () vs .done ()

473

Ich habe über aufgeschobene und versprochene jQuery-Angebote gelesen und kann den Unterschied zwischen der Verwendung von .then()& .done()für erfolgreiche Rückrufe nicht erkennen. Ich weiß, dass Eric Hynds dies erwähnt .done()und .success()derselben Funktionalität zugeordnet ist, aber ich vermute , dass dies der Fall ist, .then()da alle Rückrufe nach Abschluss eines erfolgreichen Vorgangs aufgerufen werden.

Kann mir bitte jemand die richtige Verwendung aufklären?

screenm0nkey
quelle
15
Bitte beachten Sie, dass JQuery 3.0, das im Juni 2016 veröffentlicht wurde, die erste Version war, die den Promises / A + - und ES2015 Promises-Spezifikationen entspricht. Die vorherige Implementierung war nicht mit den Versprechungen vereinbar, die gehalten werden sollten.
Flimm
Ich habe meine Antwort mit einer verbesserten Empfehlung aktualisiert, was wann verwendet werden soll.
Robert Siemer

Antworten:

577

Die an angehängten Rückrufe done()werden ausgelöst, wenn der Aufschub behoben ist. Die an angehängten Rückrufe fail()werden ausgelöst, wenn der Aufschub abgelehnt wird.

Vor jQuery 1.8 then()war nur syntaktischer Zucker:

promise.then( doneCallback, failCallback )
// was equivalent to
promise.done( doneCallback ).fail( failCallback )

Ab 1,8, then()ist ein Alias für pipe()und gibt ein neues Versprechen finden Sie hier für weitere Informationen über pipe().

success()und error()sind nur für das jqXHRObjekt verfügbar, das durch einen Aufruf an zurückgegeben wird ajax(). Sie sind einfach Aliase für done()und fail()jeweils:

jqXHR.done === jqXHR.success
jqXHR.fail === jqXHR.error

Ist auch done()nicht auf einen einzelnen Rückruf beschränkt und filtert Nichtfunktionen heraus (obwohl es in Version 1.8 einen Fehler mit Zeichenfolgen gibt, der in 1.8.1 behoben werden sollte):

// this will add fn1 to 7 to the deferred's internal callback list
// (true, 56 and "omg" will be ignored)
promise.done( fn1, fn2, true, [ fn3, [ fn4, 56, fn5 ], "omg", fn6 ], fn7 );

Gleiches gilt für fail().

Julian Aubourg
quelle
8
thenEin neues Versprechen zurückzugeben war eine Schlüsselsache, die mir fehlte. Ich konnte nicht verstehen, warum eine Kette wie $.get(....).done(function(data1) { return $.get(...) }).done(function(data2) { ... })mit data2undefiniert versagte ; Als ich dazu wechselte done, thenfunktionierte es, weil ich wirklich Versprechen zusammenpfeifen wollte, anstatt mehr Handler an das ursprüngliche Versprechen anzuhängen.
Wrschneider
5
jQuery 3.0 ist die erste Version, die den Spezifikationen Promises / A + und ES2015 entspricht.
Flimm
4
Ich verstehe immer noch nicht, warum ich eins über das andere verwenden würde. Wenn ich einen Ajax-Anruf tätige und warten muss, bis dieser Anruf vollständig abgeschlossen ist (dh die Antwort wird vom Server zurückgegeben), bevor ich einen weiteren Ajax-Anruf anrufe, verwende ich doneoder then? Warum?
CodingYoshi
@CodingYoshi Schauen Sie sich meine Antwort an, um diese Frage endgültig zu beantworten (verwenden .then()).
Robert Siemer
413

Es gibt auch Unterschiede in der Art und Weise, wie Rückgabeergebnisse verarbeitet werden (die so genannte Verkettung verkettet donenicht, während Aufrufketten thenerzeugt werden).

promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return 123;
}).then(function (x){
    console.log(x);
}).then(function (x){
    console.log(x)
})

Die folgenden Ergebnisse werden protokolliert:

abc
123
undefined

Während

promise.done(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return 123;
}).done(function (x){
    console.log(x);
}).done(function (x){
    console.log(x)
})

wird folgendes bekommen:

abc
abc
abc

---------- Update:

Übrigens. Ich habe vergessen zu erwähnen, dass das äußere Versprechen wartet, bis das innere Versprechen aufgelöst ist, wenn Sie ein Versprechen anstelle eines atomaren Wertes zurückgeben:

promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return $http.get('/some/data').then(function (result) {
        console.log(result); // suppose result === "xyz"
        return result;
    });
}).then(function (result){
    console.log(result); // result === xyz
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})

Auf diese Weise wird es sehr einfach, parallele oder sequentielle asynchrone Operationen zu erstellen, wie z.

// Parallel http requests
promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);

    var promise1 = $http.get('/some/data?value=xyz').then(function (result) {
        console.log(result); // suppose result === "xyz"
        return result;
    });

    var promise2 = $http.get('/some/data?value=uvm').then(function (result) {
        console.log(result); // suppose result === "uvm"
        return result;
    });

    return promise1.then(function (result1) {
        return promise2.then(function (result2) {
           return { result1: result1, result2: result2; }
        });
    });
}).then(function (result){
    console.log(result); // result === { result1: 'xyz', result2: 'uvm' }
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})

Der obige Code gibt zwei http-Anforderungen parallel aus, wodurch die Anforderungen früher abgeschlossen werden, während die folgenden http-Anforderungen nacheinander ausgeführt werden, wodurch die Serverlast verringert wird

// Sequential http requests
promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);

    return $http.get('/some/data?value=xyz').then(function (result1) {
        console.log(result1); // suppose result1 === "xyz"
        return $http.get('/some/data?value=uvm').then(function (result2) {
            console.log(result2); // suppose result2 === "uvm"
            return { result1: result1, result2: result2; };
        });
    });
}).then(function (result){
    console.log(result); // result === { result1: 'xyz', result2: 'uvm' }
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})
Lu4
quelle
121
+1 für den Begriff, doneder nichts mit dem Ergebnis zu tun hat, wenn thensich das Ergebnis ändert. Riesiger Punkt von den anderen imo verpasst.
Shanimal
9
Es ist wahrscheinlich erwähnenswert, für welche Version von jQuery dies gilt, da sich das Verhalten von thenin 1.8
bradley.ayers
4
+1 Auf den Punkt gebracht. Ich habe ein lauffähiges Beispiel erstellt, wenn jemand sehen möchte, welche Ketten mit gemischten doneund thenaufgerufenen Ergebnissen resultieren.
Michael Kropat
7
Das obige Beispiel hebt auch hervor, dass "erledigt" mit dem ursprünglich erstellten ursprünglichen Versprechungsobjekt funktioniert, "dann" jedoch ein neues Versprechen zurückgibt.
Pulak Kanti Bhattacharyya
2
Dies gilt für jQuery 1.8+. Ältere Versionen verhalten sich genauso wie im doneBeispiel. Wechseln Sie thenzu pipePre-1.8, um das thenVerhalten 1.8+ zu erhalten.
David Harkness
57

.done() hat nur einen Rückruf und es ist der Erfolgsrückruf

.then() hat sowohl erfolgreiche als auch fehlgeschlagene Rückrufe

.fail() hat nur einen Fail-Callback

Es liegt also an Ihnen, was Sie tun müssen. Interessiert es Sie, ob es erfolgreich ist oder ob es fehlschlägt?

Marino Šimić
quelle
18
Sie erwähnen nicht, dass "dann" Anrufketten erzeugt. Siehe Lu4s Antwort.
Oligofren
Ihre Antwort stammt aus dem Jahr 2011 ... Heutzutage unterscheiden sich ihre Rückgabewerte then()stark von done(). Wie then()oft nur beim Erfolgsrückruf genannt wird, ist Ihr Punkt eher ein Detail als die Hauptsache, an die Sie sich erinnern / wissen sollten. (Ich kann nicht sagen, wie es vor jQuery 3.0 war.)
Robert Siemer
14

deferred.done ()

Fügt Handler hinzu, die nur aufgerufen werden , wenn Deferred behoben ist . Sie können mehrere zurückzurufende Rückrufe hinzufügen.

var url = 'http://jsonplaceholder.typicode.com/posts/1';
$.ajax(url).done(doneCallback);

function doneCallback(result) {
    console.log('Result 1 ' + result);
}

Sie können auch oben so schreiben,

function ajaxCall() {
    var url = 'http://jsonplaceholder.typicode.com/posts/1';
    return $.ajax(url);
}

$.when(ajaxCall()).then(doneCallback, failCallback);

deferred.then ()

Fügt Handler hinzu, die aufgerufen werden sollen, wenn Zurückgestellt aufgelöst, abgelehnt oder noch ausgeführt wird .

var url = 'http://jsonplaceholder.typicode.com/posts/1';
$.ajax(url).then(doneCallback, failCallback);

function doneCallback(result) {
    console.log('Result ' + result);
}

function failCallback(result) {
    console.log('Result ' + result);
}
Nipuna
quelle
Ihr Beitrag macht nicht klar, wie sich thenverhält, wenn kein failRückruf bereitgestellt wird - nämlich den failFall überhaupt nicht zu erfassen
BM
Der Fehlerfall löst eine Ausnahme aus, die von der obersten Ebene des Programms abgefangen werden kann. Sie können die Ausnahme auch in der JavaScript-Konsole sehen.
David Spector
10

Es gibt tatsächlich einen ziemlich kritischen Unterschied, da jQuery's Deferreds eine Implementierung von Promises sein sollen (und jQuery3.0 tatsächlich versucht, sie in die Spezifikation zu bringen).

Der Hauptunterschied zwischen erledigt / dann ist das

  • .done() Gibt IMMER dieselben Promise / Wrapped-Werte zurück, mit denen es begonnen hat, unabhängig davon, was Sie tun oder was Sie zurückgeben.
  • .then() Gibt immer ein NEUES Versprechen zurück, und Sie sind dafür verantwortlich, zu steuern, was dieses Versprechen basierend auf der Funktion, die Sie übergeben haben, zurückgegeben hat.

Die Übersetzung von jQuery in native ES2015-Versprechungen .done()ähnelt der Implementierung einer "Tap" -Struktur um eine Funktion in einer Versprechenskette, indem sie einen Wert an eine Funktion übergibt , wenn sich die Kette im Zustand "Auflösen" befindet. Das Ergebnis dieser Funktion wirkt sich jedoch NICHT auf die Kette selbst aus.

const doneWrap = fn => x => { fn(x); return x };

Promise.resolve(5)
       .then(doneWrap( x => x + 1))
       .then(doneWrap(console.log.bind(console)));

$.Deferred().resolve(5)
            .done(x => x + 1)
            .done(console.log.bind(console));

Diese werden beide 5 protokollieren, nicht 6.

Beachten Sie, dass ich done und doneWrap zum Protokollieren verwendet habe, nicht .then. Das liegt daran, dass die Funktionen console.log eigentlich nichts zurückgeben. Und was passiert, wenn Sie eine Funktion übergeben, die nichts zurückgibt?

Promise.resolve(5)
       .then(doneWrap( x => x + 1))
       .then(console.log.bind(console))
       .then(console.log.bind(console));

Das wird protokollieren:

5

nicht definiert

Was ist passiert? Wenn ich .then verwendet und ihm eine Funktion übergeben habe, die nichts zurückgegeben hat, war das implizite Ergebnis "undefined" ... was natürlich ein Versprechen [undefined] an die nächste then-Methode zurückgab, die undefined protokollierte. Der ursprüngliche Wert, mit dem wir begonnen haben, ging also im Grunde verloren.

.then()ist im Kern eine Form der Funktionszusammensetzung: Das Ergebnis jedes Schritts wird im nächsten Schritt als Argument für die Funktion verwendet. Aus diesem Grund wird .done am besten als "Tippen" betrachtet -> es ist nicht Teil der Komposition, sondern nur etwas, das einen Blick auf den Wert in einem bestimmten Schritt wirft und eine Funktion mit diesem Wert ausführt, sich aber nicht wirklich ändert die Komposition in irgendeiner Weise.

Dies ist ein ziemlich grundlegender Unterschied, und es gibt wahrscheinlich einen guten Grund, warum native Promises keine .done-Methode selbst implementiert haben. Wir müssen uns nicht damit befassen, warum es keine .fail-Methode gibt, da dies noch komplizierter ist (nämlich .fail / .catch sind KEINE Spiegel von .done / .then -> -Funktionen in .catch, die keine bloßen Werte zurückgeben "bleib" abgelehnt wie die an .then, sie beschließen!)

Dtipson
quelle
6

then()bedeutet immer, dass es in jedem Fall aufgerufen wird. Die übergebenen Parameter sind jedoch in verschiedenen jQuery-Versionen unterschiedlich.

Vor jQuery 1.8 ist then()gleichdone().fail() . Alle Rückruffunktionen haben dieselben Parameter.

Ab jQuery 1.8 then() ein neues Versprechen zurückgegeben. Wenn es einen Wert zurückgibt, wird es an die nächste Rückruffunktion übergeben.

Sehen wir uns das folgende Beispiel an:

var defer = jQuery.Deferred();

defer.done(function(a, b){
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
}).then(function( a, b ) {
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
}).then(function( a, b ) {
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
});

defer.resolve( 3, 4 );

Vor jQuery 1.8 sollte die Antwort lauten

result = 3
result = 3
result = 3

Alles resultdauert 3. Undthen() Funktion übergibt immer das gleiche zurückgestellte Objekt an die nächste Funktion.

Ab jQuery 1.8 sollte das Ergebnis jedoch Folgendes sein:

result = 3
result = 7
result = NaN

Da die erste then()Funktion ein neues Versprechen zurückgibt und der Wert 7 (und dies ist der einzige Parameter, der weitergegeben wird) an die nächste übergeben wird done(), wird der zweite done()geschrieben result = 7. Die zweite then()nimmt 7 als Wert von aund nimmt undefinedals Wert von b, so dass die zweite then()ein neues Versprechen mit dem Parameter NaN zurückgibt und die letzte done()NaN als Ergebnis druckt.

JasmineOT
quelle
"then () bedeutet immer, dass es in jedem Fall aufgerufen wird" - nicht wahr. then () wird im Falle eines Fehlers innerhalb des Versprechens niemals aufgerufen.
David Spector
Interessanter Aspekt, dass a jQuery.Deferred()mehrere Werte empfangen kann, die es ordnungsgemäß an den ersten weitergibt .then(). - Ein wenig seltsam ... da jede Folge .then()dies nicht kann. (Die ausgewählte Schnittstelle über returnkann nur einen Wert zurückgeben.) Das native Javascript Promisemacht das nicht. (Was konsequenter ist, um ehrlich zu sein.)
Robert Siemer
3

Es gibt eine sehr einfache mentale Zuordnung als Antwort, die in den anderen Antworten etwas schwer zu finden war:

BM
quelle
2

Benutz nur .then()

Dies sind die Nachteile von .done()

  • kann nicht angekettet werden
  • Blockaufruf resolve()(alle .done()Handler werden synchron ausgeführt)
  • resolve()möglicherweise eine Ausnahme von registrierten .done()Handlern (!)
  • eine Ausnahme in einer .done()halben Tötung der aufgeschobenen:
    • Weitere .done()Handler werden stillschweigend übersprungen

Ich dachte vorübergehend, dass dies .then(oneArgOnly)immer erforderlich ist, .catch()damit keine Ausnahme stillschweigend ignoriert wird, aber das stimmt nicht mehr: Das unhandledrejectionEreignis protokolliert nicht behandelte .then()Ausnahmen auf der Konsole (standardmäßig). Sehr vernünftig! Kein Grund mehr zu verwenden .done().

Beweis

Das folgende Codefragment zeigt, dass:

  • Alle .done()Handler werden zum Zeitpunkt von als synchron bezeichnetresolve()
    • protokolliert als 1, 3, 5, 7
    • protokolliert, bevor das Skript durch den Boden fällt
  • Ausnahme in einem .done()beeinflusst resolve()Anrufer
    • über catch around angemeldet resolve()
  • Ausnahme bricht Versprechen von weiterer .done()Auflösung
    • 8 und 10 werden nicht protokolliert!
  • .then() hat keines dieser Probleme
    • wird als 2, 4, 6, 9, 11 protokolliert, nachdem der Thread im Leerlauf ist
    • (Snippet-Umgebung hat keine unhandledrejectionis scheint)

Übrigens können Ausnahmen von .done()nicht richtig abgefangen werden: Aufgrund des synchronen Musters von .done()wird der Fehler entweder an der Stelle .resolve()(möglicherweise Bibliothekscode!) Oder beim .done()Aufruf ausgelöst, der den Täter anhängt, wenn die Zurückstellung bereits behoben ist.

console.log('Start of script.');
let deferred = $.Deferred();
// deferred.resolve('Redemption.');
deferred.fail(() => console.log('fail()'));
deferred.catch(()=> console.log('catch()'));
deferred.done(() => console.log('1-done()'));
deferred.then(() => console.log('2-then()'));
deferred.done(() => console.log('3-done()'));
deferred.then(() =>{console.log('4-then()-throw');
    throw 'thrown from 4-then()';});
deferred.done(() => console.log('5-done()'));
deferred.then(() => console.log('6-then()'));
deferred.done(() =>{console.log('7-done()-throw');
    throw 'thrown from 7-done()';});
deferred.done(() => console.log('8-done()'));
deferred.then(() => console.log('9-then()'));

console.log('Resolving.');
try {
    deferred.resolve('Solution.');
} catch(e) {
    console.log(`Caught exception from handler
        in resolve():`, e);
}
deferred.done(() => console.log('10-done()'));
deferred.then(() => console.log('11-then()'));
console.log('End of script.');
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha384-vk5WoKIaW/vJyUAd9n/wmopsmNhiy+L2Z+SBxGYnUkunIxVxAv/UtMOhba/xskxh"
crossorigin="anonymous"
></script>

Robert Siemer
quelle
Ein paar Dinge: 1) Ich sehe, was Sie sagen, donedas nicht ausgeführt wird, wenn eine vorherige Ausführung eine Ausnahme hat. Aber warum sollte es stillschweigend ignoriert werden? Ich meine, es ist eine Ausnahme aufgetreten. Warum sagen Sie, dass es still ist? 2) Ich verachte das DeferredObjekt, weil seine API sehr, sehr schlecht gemacht ist. Es ist zu komplex und verwirrend. Ihr Code hier hilft auch nicht, Ihren Standpunkt zu beweisen, und er hat zu viel unnötige Komplexität für das, was Sie zu beweisen versuchen. 3) Warum werden die doneat Index 2, 4 und 6 vor dem 2. durchgeführt then?
CodingYoshi
Mein schlechtes, du hast definitiv eine Abstimmung verdient. Was Ihren Kommentar zur Ausnahme betrifft, so funktionieren Ausnahmen normalerweise so: Einmal ausgelöst, wird Code, nachdem er ausgelöst wurde, nicht mehr ausgeführt. Außerdem heißt es in der jQuery-Dokumentation, dass sie nur ausgeführt wird, wenn der Aufschub behoben ist.
CodingYoshi
@CodingYoshi Hier ist die Situation anders: Ich habe nur über gelöste Versprechen / Aufschübe gesprochen. Ich beschwere mich nicht, dass der Rest des Erfolgs-Handlers nicht angerufen wird, das ist normal. Aber ich sehe keinen Grund, warum ein völlig anderer Erfolgsverantwortlicher für ein erfolgreiches Versprechen nicht genannt wird. Alle .then()werden aufgerufen, Ausnahme (in diesen Handlern) ausgelöst oder nicht. Aber Zugabe / verbleibende .done()Pause.
Robert Siemer
@CodingYoshi Ich habe meine Antwort stark verbessert, wenn ich sagen darf. Code und Text.
Robert Siemer
1

Ab jQuery 3.0 gibt es noch einen entscheidenden Unterschied, der leicht zu unerwartetem Verhalten führen kann und in früheren Antworten nicht erwähnt wurde:

Betrachten Sie den folgenden Code:

let d = $.Deferred();
d.done(() => console.log('then'));
d.resolve();
console.log('now');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>

Dies wird Folgendes ausgeben:

then
now

Nun ersetzen done()durch then()im gleichen Schnipsel:

var d = $.Deferred();
d.then(() => console.log('then'));
d.resolve();
console.log('now');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>

Ausgabe ist jetzt:

now
then

Bei sofort aufgelösten Verzögerungen wird die übergebene Funktion done()immer synchron aufgerufen, während jedes übergebene Argument then()asynchron aufgerufen wird.

Dies unterscheidet sich von früheren jQuery-Versionen, bei denen beide Rückrufe synchron aufgerufen werden, wie im Upgrade-Handbuch erwähnt :

Eine weitere Verhaltensänderung, die für die Einhaltung von Promises / A + erforderlich ist, besteht darin, dass verzögerte .then () -Rückrufe immer asynchron aufgerufen werden. Wenn zuvor ein .then () - Rückruf zu einem Deferred hinzugefügt wurde, der bereits aufgelöst oder abgelehnt wurde, wurde der Rückruf sofort und synchron ausgeführt.

schellmax
quelle
-5

.done()Beendet die Versprechenskette und stellt sicher, dass nichts anderes weitere Schritte anhängen kann. Dies bedeutet, dass die Implementierung des jQuery-Versprechens jede nicht behandelte Ausnahme auslösen kann, da niemand damit umgehen kann.fail() .

In der Praxis sollten Sie verwenden, wenn Sie nicht vorhaben, einem Versprechen weitere Schritte beizufügen .done(). Weitere Einzelheiten erfahren Sie, warum Versprechungen gemacht werden müssen

gleb bahmutov
quelle
6
Vorsicht! Diese Antwort wäre für mehrere Versprechen-Implementierungen richtig, jedoch nicht für jQuery, bei dem .done()keine abschließende Rolle spielt. In der Dokumentation heißt es: "Da deferred.done () das zurückgestellte Objekt zurückgibt, können andere Methoden des zurückgestellten Objekts an dieses verkettet werden, einschließlich zusätzlicher .done () -Methoden." .fail()wird nicht erwähnt, aber das könnte auch verkettet werden.
Roamer-1888
1
Mein schlechtes, überprüfte nicht die jQuery
gleb bahmutov
1
@glebbahmutov - Vielleicht solltest du diese Antwort löschen, damit andere nicht verwirrt werden? Nur ein freundlicher Vorschlag :)
Andrey
2
Bitte löschen Sie die Antwort nicht, dies kann auch dazu beitragen, ihre Missverständnisse auszuräumen.
Melissa