webentwicklung-frage-antwort-db.com.de

Warum kann ich keinen Promise.catch-Handler verwenden?

Warum kann ich nicht einfach ein Error in den catch-Rückruf werfen und den Prozess den Fehler behandeln lassen, als wäre er in einem anderen Bereich?

Wenn ich console.log(err) nicht mache, wird nichts ausgedruckt und ich weiß nichts darüber, was passiert ist. Der Prozess endet gerade ...

Beispiel:

function do1() {
    return new Promise(function(resolve, reject) {
        throw new Error('do1');
        setTimeout(resolve, 1000)
    });
}

function do2() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            reject(new Error('do2'));
        }, 1000)
    });
}

do1().then(do2).catch(function(err) {
    //console.log(err.stack); // This is the only way to see the stack
    throw err; // This does nothing
});

Wenn Callbacks im Haupt-Thread ausgeführt werden, warum wird Error von einem schwarzen Loch verschluckt?

117
demian85

Wie andere erklärt haben, ist das "Schwarze Loch", weil es in ein .catch setzt die Kette mit einem abgelehnten Versprechen fort, und Sie haben keine Fänge mehr, was zu einer nicht abgeschlossenen Kette führt, die Fehler verschluckt (schlecht!)

Fügen Sie einen weiteren Fang hinzu, um zu sehen, was passiert:

do1().then(do2).catch(function(err) {
    //console.log(err.stack); // This is the only way to see the stack
    throw err; // Where does this go?
}).catch(function(err) {
    console.log(err.stack); // It goes here!
});

Ein Haken in der Mitte einer Kette ist nützlich, wenn Sie möchten, dass die Kette trotz eines fehlgeschlagenen Schritts fortgesetzt wird. Ein erneuter Wurf ist jedoch hilfreich für weiterhin fehlgeschlagen, nachdem Sie beispielsweise Informationen protokolliert oder aufgeräumt haben Schritte, vielleicht sogar ändern, welcher Fehler geworfen wird.

Trick

Um den Fehler in der Webkonsole als Fehler anzuzeigen, wie Sie es ursprünglich beabsichtigt hatten, benutze ich diesen Trick:

.catch(function(err) { setTimeout(function() { throw err; }); });

Sogar die Zeilennummern überleben, so dass der Link in der Webkonsole mich direkt zu der Datei und Zeile führt, in der der (ursprüngliche) Fehler aufgetreten ist.

Warum es funktioniert

Jede Ausnahme in einer Funktion, die als Handler für die Erfüllung oder Ablehnung von Zusagen bezeichnet wird, wird automatisch in eine Ablehnung der Zusage umgewandelt, die Sie zurückgeben sollen. Dafür sorgt der Promise-Code, der Ihre Funktion aufruft.

Eine von setTimeout aufgerufene Funktion wird dagegen immer vom stabilen JavaScript-Status ausgeführt, d. H. Sie wird in einem neuen Zyklus in der JavaScript-Ereignisschleife ausgeführt. Ausnahmen dort werden von nichts aufgefangen und schaffen es in die Webkonsole. Da err alle Informationen zu dem Fehler enthält, einschließlich des ursprünglichen Stapels, der Datei und der Zeilennummer, wird der Fehler weiterhin korrekt gemeldet.

148
jib

Wichtige Dinge, die hier zu verstehen sind

  1. Die beiden Funktionen then und catch geben neue Versprechungsobjekte zurück.

  2. Durch Werfen oder explizites Ablehnen wird das aktuelle Versprechen in den Status "Abgelehnt" versetzt.

  3. Da then und catch neue Versprechungsobjekte zurückgeben, können sie verkettet werden.

  4. Wenn Sie in einen Versprechen-Handler (then oder catch) werfen oder ihn ablehnen, wird er im nächsten Ablehnungs-Handler entlang des Verkettungspfads behandelt.

  5. Wie von jfriend00 erwähnt, werden die Handler then und catch nicht synchron ausgeführt. Wenn ein Handler wirft, endet er sofort. Der Stapel wird also abgewickelt, und die Ausnahme geht verloren. Aus diesem Grund lehnt das Auslösen einer Ausnahme das aktuelle Versprechen ab.


In Ihrem Fall lehnen Sie in do1 Ab, indem Sie ein Error -Objekt auslösen. Jetzt wird das aktuelle Versprechen abgelehnt und die Kontrolle an den nächsten Handler übergeben, in unserem Fall then.

Da der then -Handler keinen Ablehnungshandler hat, wird der do2 Überhaupt nicht ausgeführt. Sie können dies mit console.log Bestätigen. Da das aktuelle Versprechen keinen Ablehnungs-Handler hat, wird es auch mit dem Ablehnungswert aus dem vorherigen Versprechen abgelehnt und die Kontrolle wird an den nächsten Handler übertragen, der catch ist.

Da catch ein Zurückweisungshandler ist, können Sie, wenn Sie console.log(err.stack); darin ausführen, die Fehler-Stack-Ablaufverfolgung sehen. Jetzt werfen Sie ein Error -Objekt daraus, sodass das von catch zurückgegebene Versprechen ebenfalls abgelehnt wird.

Da Sie dem catch keinen Ablehnungshandler hinzugefügt haben, können Sie die Ablehnung nicht beobachten.


Sie können die Kette aufteilen und dies besser verstehen

var promise = do1().then(do2);

var promise1 = promise.catch(function (err) {
    console.log("Promise", promise);
    throw err;
});

promise1.catch(function (err) {
    console.log("Promise1", promise1);
});

Die Ausgabe, die Sie erhalten, ist ungefähr so

Promise Promise { <rejected> [Error: do1] }
Promise1 Promise { <rejected> [Error: do1] }

Innerhalb des catch -Handlers 1 erhalten Sie den Wert des promise -Objekts als abgelehnt.

Ebenso wird das Versprechen, das vom catch -Handler 1 zurückgegeben wurde, mit demselben Fehler zurückgewiesen, mit dem der promise zurückgewiesen wurde, und wir beobachten es im zweiten catch -Handler .

41
thefourtheye

Ich habe die oben beschriebene Methode setTimeout() ausprobiert ...

.catch(function(err) { setTimeout(function() { throw err; }); });

Ärgerlicherweise fand ich das völlig unprüfbar. Da es einen asynchronen Fehler auslöst, können Sie ihn nicht in ein try/catch -Anweisung, da catch beim Auslösen des Zeitfehlers aufgehört hat zu lauschen.

Ich habe mich darauf beschränkt, einen Listener zu verwenden, der perfekt funktioniert und der, da JavaScript so verwendet werden soll, in hohem Maße testbar ist.

return new Promise((resolve, reject) => {
    reject("err");
}).catch(err => {
    this.emit("uncaughtException", err);

    /* Throw so the promise is still rejected for testing */
    throw err;
});
3
RiggerTheGeek

Nach die Spezifikation (siehe 3.III.d) :

d. Wenn der Aufruf dann eine Ausnahme e auslöst,
ein. Wenn resolvePromise oder rejectPromise aufgerufen wurden, ignorieren Sie es.
B. Andernfalls lehnen Sie das Versprechen mit e als Grund ab.

Das heißt, wenn Sie eine Ausnahme in der Funktion then auslösen, wird sie abgefangen und Ihr Versprechen wird abgelehnt. catch ergibt hier keinen Sinn, es ist nur eine Verknüpfung zu .then(null, function() {})

Ich nehme an, Sie möchten nicht bearbeitete Ablehnungen in Ihrem Code protokollieren. Die meisten Versprechungen Bibliotheken feuert ein unhandledRejection dafür. Hier ist relevantes Gist mit Diskussion darüber.

2
just-boris

Ich weiß, dass es etwas spät ist, aber ich bin auf diesen Thread gestoßen, und keine der Lösungen war einfach für mich zu implementieren.

Ich habe eine kleine Hilfefunktion hinzugefügt, die ein Versprechen zurückgibt:

function throw_promise_error (error) {
 return new Promise(function (resolve, reject){
  reject(error)
 })
}

Wenn ich dann eine bestimmte Stelle in meiner Versprechen-Kette habe, an der ich einen Fehler werfen möchte (und das Versprechen ablehne), kehre ich einfach mit meinem konstruierten Fehler von der obigen Funktion zurück:

}).then(function (input) {
 if (input === null) {
  let err = {code: 400, reason: 'input provided is null'}
  return throw_promise_error(err)
 } else {
  return noterrorpromise...
 }
}).then(...).catch(function (error) {
 res.status(error.code).send(error.reason);
})

Auf diese Weise kann ich zusätzliche Fehler aus der Versprechen-Kette herauswerfen. Wenn Sie auch "normale" Versprechungsfehler behandeln möchten, erweitern Sie Ihren Fang, um die "selbst geworfenen" Fehler separat zu behandeln.

Hoffe das hilft, es ist meine erste Stackoverflow-Antwort!

1
nuudles

Ja, Versprechungen verschlucken Fehler und Sie können sie nur mit .catch Abfangen, wie in anderen Antworten ausführlicher erläutert. Wenn Sie sich in Node.js befinden und das normale Verhalten von throw reproduzieren möchten, können Sie den Stack-Trace zur Konsole drucken und den Prozess beenden

...
  throw new Error('My error message');
})
.catch(function (err) {
  console.error(err.stack);
  process.exit(0);
});
0
Jesús Carrera