Wann wird .then (Erfolg, Misserfolg) als Antimuster für Versprechen angesehen?

188

Ich habe mir die FAQ zu Bluebird-Versprechungen angesehen , in der erwähnt wird, dass .then(success, fail)es sich um ein Antimuster handelt . Ich verstehe die Erklärung für das Versuchen und Fangen nicht ganz. Was ist daran folgendes falsch?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

Es scheint, dass das Beispiel Folgendes als den richtigen Weg vorschlägt.

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

Was ist der Unterschied?

user2127480
quelle
1
then().catch()ist besser lesbar, da Sie nicht nach Komma suchen und untersuchen müssen, ob dieser Rückruf erfolgreich ist oder fehlschlägt.
Krzysztof Safjanowski
7
@ KevinB: Es gibt einen großen Unterschied, überprüfen Sie die Antworten
Bergi
12
@KrzysztofSafjanowski - am Boden zerstört durch das Argument "sieht besser aus". Total falsch!
Andrey Popov
6
HINWEIS: Wenn Sie verwenden .catch, wissen Sie nicht, welcher Schritt das Problem verursacht hat - innerhalb des letzten thenoder irgendwo anders in der Versprechenskette. Es hat also seinen eigenen Nachteil.
Vitaly-t
2
Ich füge dem Versprechen .then () immer Funktionsnamen hinzu, um es lesbar zu machen, dhsome_promise_call() .then(function fulfilled(res) { logger.log(res) }, function rejected(err) { logger.log(err) })
Shane Rowatt

Antworten:

215

Was ist der Unterschied?

Der .then()Anruf gibt ein Versprechen zurück, das abgelehnt wird, falls der Rückruf einen Fehler auslöst. Wenn Ihr Erfolg loggerfehlschlägt, wird der Fehler an den folgenden .catch()Rückruf weitergeleitet, jedoch nicht an den faildazugehörigen Rückruf success.

Hier ist ein Kontrollflussdiagramm :

Kontrollflussdiagramm von dann mit zwei Argumenten Kontrollflussdiagramm der dann Fangkette

Um es in synchronem Code auszudrücken:

// some_promise_call().then(logger.log, logger.log)
then: {
    try {
        var results = some_call();
    } catch(e) {
        logger.log(e);
        break then;
    } // else
        logger.log(results);
}

Das zweite log(wie das erste Argument .then()) wird nur ausgeführt, wenn keine Ausnahme aufgetreten ist. Der markierte Block und die breakAussage fühlen sich ein wenig seltsam, das ist eigentlich das, was Python hat try-except-elsefür (empfohlene Lektüre!).

// some_promise_call().then(logger.log).catch(logger.log)
try {
    var results = some_call();
    logger.log(results);
} catch(e) {
    logger.log(e);
}

Der catchLogger behandelt auch Ausnahmen vom erfolgreichen Logger-Aufruf.

Soviel zum Unterschied.

Ich verstehe die Erklärung für das Versuchen und Fangen nicht ganz

Das Argument ist, dass Sie normalerweise Fehler in jedem Schritt der Verarbeitung abfangen möchten und dass Sie sie nicht in Ketten verwenden sollten. Die Erwartung ist, dass Sie nur einen letzten Handler haben, der alle Fehler behandelt. Wenn Sie das "Antipattern" verwenden, werden Fehler in einigen der damaligen Rückrufe nicht behandelt.

Dieses Muster ist jedoch tatsächlich sehr nützlich: Wenn Sie Fehler behandeln möchten, die genau in diesem Schritt aufgetreten sind, und etwas ganz anderes tun möchten, wenn kein Fehler aufgetreten ist - dh wenn der Fehler nicht behoben werden kann. Beachten Sie, dass dies Ihren Kontrollfluss verzweigt . Natürlich ist dies manchmal erwünscht.


Was ist daran folgendes falsch?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

Dass Sie Ihren Rückruf wiederholen mussten. Du willst lieber

some_promise_call()
   .catch(function(e) {
       return e; // it's OK, we'll just log it
   })
   .done(function(res) {
       logger.log(res);
   });

Sie können dies auch in Betracht ziehen .finally().

Bergi
quelle
7
Dies ist die hilfreichste Erklärung, die ich in ein paar Tagen gelesen habe (und ich habe viel gelesen). Ich kann nicht erklären, wie dankbar ich bin! :) Ich denke , Sie sollten mehr den Unterschied zwischen den beiden stressen, die .catchwerden Fehler auch in der Erfolgsfunktion fangen .. Ich persönlich finde das extrem falsch , wie Sie mit einem Fehler-Eintrittspunkt am Ende, die von bekommen können mehrere Fehler mehrere Aktionen, aber das ist mein Problem. Sowieso - danke für die Info! Haben Sie kein Online-Kommunikationstool, das Sie gerne teilen möchten, damit ich noch ein paar Fragen stellen kann? : P
Andrey Popov
2
Ich hoffe, das gibt Ihnen hier weitere positive Stimmen. Auf jeden Fall eine der besten Erklärungen für einen wichtigen PromiseMechaniker auf dieser Seite.
Patrick Roberts
2
.done()ist nicht Teil des Standards, oder? Zumindest listet MDN diese Methode nicht auf. Es wäre hilfreich.
Ygoe
1
@ygoe In der Tat. doneist eine Bluebird-Sache, die im Grunde genommen durch die then+ unbehandelte Ablehnungserkennung veraltet war .
Bergi
1
Nur eine Notiz von einem Farbenblinden: Die Diagramme machen keinen Sinn :)
Benny K
37

Die beiden sind nicht ganz identisch. Der Unterschied besteht darin, dass das erste Beispiel keine Ausnahme abfängt, die in Ihrem successHandler ausgelöst wird . Wenn Ihre Methode also immer nur aufgelöste Versprechen zurückgeben soll, wie dies häufig der Fall ist, benötigen Sie einen nachgestellten catchHandler (oder einen weiteren thenmit einem leeren successParameter). Sicher, es kann sein, dass Ihr thenHandler nichts tut, was möglicherweise fehlschlägt. In diesem Fall kann die Verwendung eines 2-Parameters thenin Ordnung sein.

Ich glaube jedoch, dass der Punkt des Textes, mit dem Sie verlinkt haben, im thenVergleich zu Rückrufen in Bezug auf die Fähigkeit, eine Reihe von asynchronen Schritten zu verketten, am nützlichsten ist, und wenn Sie dies tatsächlich tun, thenverhält sich die 2-Parameter-Form von subtil nicht ganz wie erwartet aus dem oben genannten Grund. Es ist besonders intuitiv, wenn es in der Mitte der Kette verwendet wird.

Als jemand, der viele komplexe asynchrone Dinge erledigt hat und mehr auf solche Ecken gestoßen ist, als ich zugeben möchte, empfehle ich wirklich, dieses Anti-Pattern zu vermeiden und den separaten Handler-Ansatz zu wählen.

acjay
quelle
18

Indem wir die Vor- und Nachteile beider betrachten, können wir eine kalkulierte Vermutung anstellen, welche für die Situation geeignet ist. Dies sind die beiden Hauptansätze zur Umsetzung von Versprechungen. Beide haben Vor- und Nachteile

Fangansatz

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

Vorteile

  1. Alle Fehler werden von einem Catch-Block behandelt.
  2. Fängt sogar eine Ausnahme im then-Block ab.
  3. Verkettung mehrerer erfolgreicher Rückrufe

Nachteile

  1. Im Falle einer Verkettung wird es schwierig, unterschiedliche Fehlermeldungen anzuzeigen.

Erfolgs- / Fehleransatz

some_promise_call()
.then(function success(res) { logger.log(res) },
      function error(err) { logger.log(err) })

Vorteile

  1. Sie erhalten eine feinkörnige Fehlerkontrolle.
  2. Sie können eine allgemeine Fehlerbehandlungsfunktion für verschiedene Fehlerkategorien wie DB-Fehler, 500-Fehler usw. verwenden.

Nachteile

  1. Sie benötigen noch einen weiteren, catchwenn Sie Fehler behandeln möchten, die durch den erfolgreichen Rückruf ausgelöst wurden
aWebDeveloper
quelle
Für jemanden, der Produktionsprobleme nur mit einer Protokolldatei debuggen muss, bevorzuge ich den Erfolgs- / Fehleransatz, da er die Möglichkeit bietet, eine kausale Fehlerkette zu erstellen, die an den Exit-Grenzen Ihrer App protokolliert werden kann.
Shane Rowatt
Frage. Nehmen wir an, ich mache einen asynchronen Aufruf, der eines der folgenden Dinge tut: 1) Rückgabe erfolgreich (2xx Statuscode), 2) Rückgabe erfolglos (4xx oder 5xx Code), aber nicht per se abgelehnt, 3) oder überhaupt nicht zurückgibt ( Internetverbindung ist unterbrochen). Für Fall 1 wird der Erfolgsrückruf in der .then getroffen. Für Fall 2 wird der Fehlerrückruf in der .then getroffen. Für Fall 3 wird der .catch aufgerufen. Das ist eine korrekte Analyse, oder? Fall Nr. 2 ist am schwierigsten, da ein 4xx oder 5xx technisch gesehen keine Ablehnung darstellt und dennoch erfolgreich zurückkehrt. Daher müssen wir innerhalb der .then damit umgehen. .... Ist mein Verständnis richtig?
Benjamin Hoffman
"Für Fall Nr. 2 wird der Fehlerrückruf in .then getroffen. In Fall Nr. 3 wird der .catch aufgerufen. Dies ist eine korrekte Analyse, oder?" - So funktioniert Fetch
aWebDeveloper
2

Einfach erklären:

In ES2018

Wenn die catch-Methode mit dem Argument onRejected aufgerufen wird, werden die folgenden Schritte ausgeführt:

  1. Lassen Sie Versprechen diesen Wert sein.
  2. Rückkehr ? Invoke (Versprechen, "dann", "undefiniert, abgelehnt").

das bedeutet:

promise.then(f1).catch(f2)

gleich

promise.then(f1).then(undefiend, f2)
bitfishxyz
quelle
1

Mit .then().catch()können Sie Promise Chaining aktivieren, das zur Erfüllung eines Workflows erforderlich ist. Möglicherweise müssen Sie einige Informationen aus der Datenbank lesen, dann möchten Sie sie an eine asynchrone API übergeben und anschließend die Antwort bearbeiten. Möglicherweise möchten Sie die Antwort zurück in die Datenbank verschieben. Die Handhabung all dieser Workflows mit Ihrem Konzept ist machbar, aber sehr schwer zu verwalten. Die bessere Lösung besteht darin then().then().then().then().catch(), alle Fehler in nur einem Fang zu erhalten und die Wartbarkeit des Codes zu gewährleisten.

Jayant Varshney
quelle
0

Verwendung then()und catch()Unterstützung der Verkettung von Erfolg und Misserfolg bei der Einhaltung des Versprechens. catch()arbeitet an Versprechen von zurückgegeben then(). Es behandelt,

  1. Wenn das Versprechen abgelehnt wurde. Siehe # 3 im Bild
  2. Wenn im Erfolgshandler von then () ein Fehler aufgetreten ist, zwischen den Zeilennummern 4 bis 7 unten. Siehe # 2.a im Bild (Fehlerrückruf ein then()behandelt dies nicht.)
  3. Wenn im Fehlerbehandler von then () ein Fehler aufgetreten ist, Zeile Nummer 8 unten. Siehe # 3.b im Bild.

1. let promiseRef: Promise = this. aTimetakingTask (false); 2. promiseRef 3. .then( 4. (result) => { 5. /* successfully, resolved promise. 6. Work on data here */ 7. }, 8. (error) => console.log(error) 9. ) 10. .catch( (e) => { 11. /* successfully, resolved promise. 12. Work on data here */ 13. });

Geben Sie hier die Bildbeschreibung ein

Hinweis : Oft wird der Fehlerbehandler möglicherweise nicht definiert, wenn catch()er bereits geschrieben wurde. EDIT: reject()Ergebnis in dem Aufruf catch()nur , wenn die Fehler - Handler in then()ist nicht definiert. Beachten Sie # 3 im Bild zum catch(). Es wird aufgerufen, wenn der Handler in Zeile 8 und 9 nicht definiert ist.

Dies ist sinnvoll, da das von zurückgegebene Versprechen then()keinen Fehler enthält, wenn sich ein Rückruf darum kümmert.

VenCKi
quelle
Der Pfeil von der Nummer 3 zum catchRückruf scheint falsch.
Bergi
Vielen Dank! Mit einem in then () definierten Fehlerrückruf wird dieser nicht aufgerufen (Zeile 8 und 9 im Code-Snippet). # 3 ruft einen der beiden Pfeile auf. Dies ist sinnvoll, da das bis dahin zurückgegebene Versprechen () keinen Fehler enthält, wenn sich ein Rückruf darum kümmert. Antwort bearbeitet!
VenCKi
-1

Anstelle von Worten ein gutes Beispiel. Folgender Code (falls das erste Versprechen gelöst wurde):

Promise.resolve()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

ist identisch mit:

Promise.resolve()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)

Aber mit abgelehntem ersten Versprechen ist dies nicht identisch:

Promise.reject()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

Promise.reject()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)
ktretyak
quelle
4
Das macht keinen Sinn. Können Sie diese Antwort bitte entfernen? Es ist irreführend und lenkt von der richtigen Antwort ab.
Andy Ray
@AndyRay, das macht in der realen Anwendung keinen Sinn, aber es ist sinnvoll, die Arbeit der Versprechen zu verstehen.
Ktretyak