Verkettete Versprechen, die Ablehnung nicht weitergeben

Lesezeit: 11 Minuten

Verkettete Versprechen die Ablehnung nicht weitergeben
Jordanien

Ich habe ein Problem damit, zu verstehen, warum Ablehnungen nicht über eine Versprechenskette weitergegeben werden, und ich hoffe, dass mir jemand helfen kann, zu verstehen, warum. Für mich impliziert das Anhängen von Funktionalität an eine Kette von Versprechen die Absicht, dass ich von der Erfüllung eines ursprünglichen Versprechens abhängig bin. Es ist schwer zu erklären, also lassen Sie mich zuerst ein Codebeispiel für mein Problem zeigen. (Hinweis: Dieses Beispiel verwendet Node und das verzögerte Knotenmodul. Ich habe dies mit Dojo 1.8.3 getestet und hatte die gleichen Ergebnisse.)

var d = require("deferred");

var d1 = d();

var promise1 = d1.promise.then(
    function(wins) { console.log('promise1 resolved'); return wins;},
    function(err) { console.log('promise1 rejected'); return err;});
var promise2 = promise1.then(
    function(wins) { console.log('promise2 resolved'); return wins;},
    function(err) { console.log('promise2 rejected'); return err;});
var promise3 = promise2.then(
    function(wins) { console.log('promise3 resolved'); return wins;},
    function(err) { console.log('promise3 rejected'); return err;});
d1.reject(new Error());

Das Ergebnis der Ausführung dieser Operation ist diese Ausgabe:

promise1 rejected
promise2 resolved
promise3 resolved

Okay, für mich ergibt dieses Ergebnis keinen Sinn. Durch das Anhängen an diese Versprechenskette impliziert jeder dann die Absicht, dass er von der erfolgreichen Lösung von d1 und einem Ergebnis abhängig sein wird, das die Kette nach unten weitergegeben wird. Wenn das Promise in Promise1 nicht den wins-Wert erhält, sondern stattdessen einen err-Wert in seinem Error-Handler, wie ist es dann möglich, dass das nächste Promise in der Kette seine Erfolgsfunktion aufruft? Es kann auf keinen Fall einen aussagekräftigen Wert an das nächste Promise weitergeben, da es selbst keinen Wert erhalten hat.

Anders kann ich beschreiben, was ich denke: Es gibt drei Personen, John, Ginger und Bob. John besitzt einen Widget-Shop. Ginger kommt in sein Geschäft und fordert eine Tüte mit Widgets in verschiedenen Farben an. Er hat sie nicht auf Lager, also sendet er eine Anfrage an seinen Händler, um sie ihm zusenden zu lassen. In der Zwischenzeit gibt er Ginger einen Regenscheck, der besagt, dass er ihr die Tüte mit Widgets schuldet. Bob findet heraus, dass Ginger die Widgets bekommt und bittet ihn, das blaue Widget zu bekommen, wenn sie damit fertig ist. Sie stimmt zu und gibt ihm eine Notiz, die besagt, dass sie es tun wird. Jetzt kann Johns Händler keine Widgets in seinem Angebot finden und der Hersteller stellt sie nicht mehr her, also informieren sie John, der wiederum Ginger mitteilt, dass sie die Widgets nicht bekommen kann. Wie kann Bob ein blaues Widget von Ginger bekommen, wenn er selbst keines bekommen hat?

Eine dritte realistischere Perspektive, die ich zu diesem Thema habe, ist folgende. Angenommen, ich habe zwei Werte, die ich in einer Datenbank aktualisieren möchte. Einer ist von der ID des anderen abhängig, aber ich kann die ID nicht erhalten, bis ich sie bereits in eine Datenbank eingefügt und das Ergebnis erhalten habe. Darüber hinaus ist die erste Einfügung von einer Abfrage aus der Datenbank abhängig. Die Datenbankaufrufe geben Versprechungen zurück, die ich verwende, um die beiden Aufrufe in einer Sequenz zu verketten.

var promise = db.query({parent_id: value});
promise.then(function(query_result) {
    var first_value = {
        parent_id: query_result[0].parent_id
    }
    var promise = db.put(first_value);
    promise.then(function(first_value_result) {
        var second_value = {
            reference_to_first_value_id: first_value_result.id
        }
        var promise = db.put(second_value);
        promise.then(function(second_value_result) {
            values_successfully_entered();
        }, function(err) { return err });
    }, function(err) { return err });
}, function(err) { return err });

Nun, in dieser Situation, wenn die db.query fehlschlug, würde sie die err-Funktion der ersten dann aufrufen. Aber dann würde es die Erfolgsfunktion des nächsten Versprechens aufrufen. Während dieses Promise die Ergebnisse des ersten Werts erwartet, würde es stattdessen die Fehlermeldung von seiner Fehlerbehandlungsfunktion erhalten.

Meine Frage ist also, warum sollte ich eine Fehlerbehandlungsfunktion haben, wenn ich auf Fehler in meiner Erfolgsfunktion testen muss?

Sorry für die Länge. Ich wusste nur nicht, wie ich es anders erklären sollte.

UPDATE und Korrektur

(Hinweis: Ich habe eine Antwort entfernt, die ich einmal auf einige Kommentare abgegeben hatte. Wenn also jemand meine Antwort kommentiert hat, scheinen ihre Kommentare jetzt, da ich sie entfernt habe, aus dem Zusammenhang gerissen zu sein. Entschuldigung dafür, ich versuche, mich so kurz wie möglich zu halten .)

Vielen Dank an alle, die geantwortet haben. Ich möchte mich zunächst bei allen dafür entschuldigen, dass ich meine Frage so schlecht formuliert habe, insbesondere meinen Pseudocode. Ich war ein wenig zu aggressiv bei dem Versuch, es kurz zu halten.

Dank Bergis Antwort glaube ich, den Fehler in meiner Logik gefunden zu haben. Ich glaube, ich habe vielleicht ein anderes Problem übersehen, das mein Problem verursacht hat. Dies führt möglicherweise dazu, dass die Promise-Kette anders funktioniert, als ich dachte. Ich teste immer noch verschiedene Elemente meines Codes, daher kann ich noch nicht einmal eine richtige Frage stellen, um zu sehen, was ich falsch mache. Ich wollte Sie alle auf den neuesten Stand bringen und danke Ihnen für Ihre Hilfe.

  • Welche Versionen verwenden Sie? Dies wird angezeigt rejected 3 mal bei mir am 0.10.0 und zeitversetzt am 0.6.3.

    – loganfsmyth

    4. Mai 2013 um 8:16 Uhr

  • Es funktioniert für mich auch auf Knoten 0.8.3 und verzögert 0.6.3 gist.github.com/Stuk/694b2377057453aa6946

    – Stuart K

    4. Mai 2013 um 16:51 Uhr

1647275710 229 Verkettete Versprechen die Ablehnung nicht weitergeben
Bergi

Für mich ergibt dieses Ergebnis keinen Sinn. Durch das Anhängen an diese Versprechenskette impliziert jeder dann die Absicht, dass er von der erfolgreichen Lösung von d1 und einem Ergebnis abhängig sein wird, das die Kette nach unten weitergegeben wird

Nein. Was Sie beschreiben, ist keine Kette, sondern nur das Anhängen aller Rückrufe d1. Doch, wenn Sie etwas mit Kette wollen thendas Ergebnis für promise2 ist von der Auflösung abhängig promise1 und wie die then Rückrufe haben es gehandhabt.

In den Dokumenten heißt es:

Gibt ein neues Promise für das Ergebnis des Callbacks zurück.

Die .then Methode wird normalerweise in Bezug auf die betrachtet Versprechen/Eine Spezifikation (oder noch strenger Versprechen/A+ eins). Das bedeutet, dass die Rückgabeversprechen der Callback-Shell assimiliert werden, um die Auflösung von zu werden promise2und wenn es keinen Erfolgs-/Fehlerbehandler gibt, wird das jeweilige Ergebnis gegebenenfalls direkt an übergeben promise2 – so können Sie einfach den Handler weglassen um den Fehler zu verbreiten.

Doch, wenn der Fehler ist abgewickeltdas Ergebnis promise2 als fest angesehen wird und mit diesem Wert erfüllt wird. Wenn du das nicht willst, müsstest du betreffend-throw der Fehler, genau wie in einer Try-Catch-Klausel. Alternativ können Sie ein (zukünftig) abgelehntes Versprechen vom Handler zurückgeben. Ich bin mir nicht sicher, wie Dojo ablehnen soll, aber:

var d1 = d();

var promise1 = d1.promise.then(
    function(wins) { console.log('promise1 resolved'); return wins;},
    function(err) { console.log('promise1 rejected'); throw err;});
var promise2 = promise1.then(
    function(wins) { console.log('promise2 resolved'); return wins;},
    function(err) { console.log('promise2 rejected'); throw err;});
var promise3 = promise2.then(
    function(wins) { console.log('promise3 resolved'); return wins;},
    function(err) { console.log('promise3 rejected'); throw err;});
d1.reject(new Error());

Wie kann Bob ein blaues Widget von Ginger bekommen, wenn er selbst keines bekommen hat?

Er soll es nicht können. Wenn es keine Fehlerbehandler gibt, wird er nur die Nachricht (((vom Verteiler) von John) von Ginger) wahrnehmen, dass es keine Widgets mehr gibt. Wenn Ginger jedoch einen Fehlerbehandler für diesen Fall einrichtet, könnte sie ihr Versprechen, Bob ein Widget zu geben, dennoch erfüllen, indem sie ihm ein grünes aus ihrer eigenen Hütte gibt, wenn bei John oder seinem Händler keine blauen übrig sind.

Um Ihre Fehlerrückrufe in die Metapher zu übersetzen, return err vom Handler wäre wie zu sagen “wenn es keine Widgets mehr gibt, geben Sie ihm einfach die Notiz, dass es keine mehr gibt – es ist so gut wie das gewünschte Widget”.

Wenn in der Datenbanksituation die db.query fehlschlug, würde sie die err-Funktion der ersten dann aufrufen

… was bedeuten würde, dass der Fehler dort behandelt wird. Wenn Sie das nicht tun, lassen Sie einfach den Fehlerrückruf weg. Übrigens, Ihre Erfolgsrückrufe nicht return die Versprechungen, die sie machen, also scheinen sie ziemlich nutzlos zu sein. Richtig wäre:

var promise = db.query({parent_id: value});
promise.then(function(query_result) {
    var first_value = {
        parent_id: query_result[0].parent_id
    }
    var promise = db.put(first_value);
    return promise.then(function(first_value_result) {
        var second_value = {
            reference_to_first_value_id: first_value_result.id
        }
        var promise = db.put(second_value);
        return promise.then(function(second_value_result) {
            return values_successfully_entered();
        });
    });
});

oder, da Sie die Closures nicht benötigen, um auf Ergebniswerte von vorherigen Callbacks zuzugreifen, sogar:

db.query({parent_id: value}).then(function(query_result) {
    return db.put({
        parent_id: query_result[0].parent_id
    });
}).then(function(first_value_result) {
    return db.put({
        reference_to_first_value_id: first_value_result.id
    });
}.then(values_successfully_entered);

  • Bei Verwendung von angleJS mit $q muss das throw-Schlüsselwort durch $q.reject(err) ersetzt werden.

    – Mühselig

    24. August 2014 um 5:21 Uhr

  • Um die Bemerkung von @ Toilal zu bereinigen, die bevorzugt Ersatz zu throwist return $q.reject(err). throw wird, glaube ich, noch funktionieren; es ist nur viel langsamer.

    – Michael Lorton

    18. Juni 2015 um 15:14 Uhr


@Jordan Erstens, wie Kommentatoren bemerkten, liefert Ihr erstes Beispiel bei Verwendung von Deferred Lib definitiv das erwartete Ergebnis:

promise1 rejected
promise2 rejected
promise3 rejected

Zweitens, selbst wenn es die von Ihnen vorgeschlagene Ausgabe erzeugen würde, würde es den Ausführungsfluss Ihres zweiten Snippets nicht beeinträchtigen, was etwas anders ist, eher wie:

promise.then(function(first_value) {
    console.log('promise1 resolved');
    var promise = db.put(first_value);
    promise.then(function (second_value) {
         console.log('promise2 resolved');
         var promise = db.put(second_value);
         promise.then(
             function (wins) { console.log('promise3 resolved'); },
             function (err) { console.log('promise3 rejected'); return err; });
    }, function (err) { console.log('promise2 rejected'); return err;});
}, function (err) { console.log('promise1 rejected'); return err});

und dass im Falle einer Ablehnung des ersten Versprechens nur Folgendes ausgegeben wird:

promise1 rejected

Aber (zum interessantesten Teil kommen), obwohl die zurückgestellte Bibliothek definitiv zurückkehrt 3 x rejectedwerden die meisten anderen Versprechungsbibliotheken zurückkehren 1 x rejected, 2 x resolved (das führt zu der Annahme, dass Sie diese Ergebnisse erhalten haben, indem Sie stattdessen eine andere Promise-Bibliothek verwendet haben).

Was zusätzlich verwirrend ist, diese anderen Bibliotheken sind mit ihrem Verhalten korrekter. Lassen Sie mich erklären.

In einer Sync-Welt ist das Gegenstück zu “Promise Rejection”. throw. Also semantisch asynchron deferred.reject(new Error()) synchron gleich throw new Error(). In Ihrem Beispiel geben Sie keine Fehler in Ihren Synchronisierungsrückrufen aus, sondern geben sie nur zurück. Daher wechseln Sie zum Erfolgsfluss, wobei ein Fehler ein Erfolgswert ist. Um sicherzustellen, dass die Ablehnung weitergereicht wird, müssen Sie Ihre Fehler erneut ausgeben:

function (err) { console.log('promise1 rejected'); throw err; });

Nun stellt sich also die Frage, warum die zurückgestellte Bibliothek den zurückgegebenen Fehler als Ablehnung akzeptiert hat?

Grund dafür ist, dass die Ablehnung bei aufgeschobenen Arbeiten etwas anders ist. Bei deferred lib gilt die Regel: Promise wird zurückgewiesen, wenn es mit einem Fehlerfall aufgelöst wirdalso selbst wenn du es tust deferred.resolve(new Error()) es wird handeln als deferred.reject(new Error())und wenn Sie versuchen, zu tun deferred.reject(notAnError) Es wird eine Ausnahme ausgelöst, die besagt, dass das Versprechen nur im Fall eines Fehlers abgelehnt werden kann. Das macht deutlich, warum der Fehler zurückgegeben wird then Rückruf lehnt das Versprechen ab.

Es gibt einige gültige Argumente für die verzögerte Logik, aber sie ist immer noch nicht gleichbedeutend mit dem Wie throw funktioniert in JavaScript, und aus diesem Grund soll dieses Verhalten mit Version v0.7 von deferred geändert werden.

Kurze Zusammenfassung:

Um Verwirrung und unerwartete Ergebnisse zu vermeiden, befolgen Sie einfach die Regeln für bewährte Verfahren:

  1. Lehnen Sie Ihre Versprechen immer mit Fehlerinstanzen ab (befolgen Sie die Regeln der Sync-Welt, wo das Werfen von Werten, die kein Fehler sind, als schlechte Praxis angesehen wird).
  2. Ablehnen von Synchronisierungsrückrufen durch werfen Fehler (ihre Rücksendung garantiert keine Ablehnung).

Wenn Sie sich an das Obige halten, erhalten Sie sowohl konsistente als auch erwartete Ergebnisse sowohl in verzögerten als auch in anderen beliebten Versprechungsbibliotheken.

Use kann die Fehler auf jeder Ebene des Versprechens umschließen. Ich habe die Fehler eingekettet TraceError:

class TraceError extends Error {
  constructor(message, ...causes) {
    super(message);

    const stack = Object.getOwnPropertyDescriptor(this, 'stack');

    Object.defineProperty(this, 'stack', {
      get: () => {
        const stacktrace = stack.get.call(this);
        let causeStacktrace="";

        for (const cause of causes) {
          if (cause.sourceStack) { // trigger lookup
            causeStacktrace += `\n${cause.sourceStack}`;
          } else if (cause instanceof Error) {
            causeStacktrace += `\n${cause.stack}`;
          } else {
            try {
              const json = JSON.stringify(cause, null, 2);
              causeStacktrace += `\n${json.split('\n').join('\n    ')}`;
            } catch (e) {
              causeStacktrace += `\n${cause}`;
              // ignore
            }
          }
        }

        causeStacktrace = causeStacktrace.split('\n').join('\n    ');

        return stacktrace + causeStacktrace;
      }
    });

    // access first error
    Object.defineProperty(this, 'cause', {value: () => causes[0], enumerable: false, writable: false});

    // untested; access cause stack with error.causes()
    Object.defineProperty(this, 'causes', {value: () => causes, enumerable: false, writable: false});
  }
}

Verwendungszweck

throw new TraceError('Could not set status', srcError, ...otherErrors);

Ausgabe

Screenshot%202016 03 01%2022.26.27

Funktionen

TraceError#cause - first error
TraceError#causes - list of chained errors

1002380cookie-checkVerkettete Versprechen, die Ablehnung nicht weitergeben

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy