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?
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.
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.
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.
Wichtige Dinge, die hier zu verstehen sind
Die beiden Funktionen then
und catch
geben neue Versprechungsobjekte zurück.
Durch Werfen oder explizites Ablehnen wird das aktuelle Versprechen in den Status "Abgelehnt" versetzt.
Da then
und catch
neue Versprechungsobjekte zurückgeben, können sie verkettet werden.
Wenn Sie in einen Versprechen-Handler (then
oder catch
) werfen oder ihn ablehnen, wird er im nächsten Ablehnungs-Handler entlang des Verkettungspfads behandelt.
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 .
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;
});
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.
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!
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);
});