throwtry-Anweisungtry-Blockcatch-Klauselfinally-KlauselError und seine UnterklassenErrorErrorErrorerror.cause [ES2022].cause: eine benutzerdefinierte FehlerklasseDieses Kapitel behandelt, wie JavaScript Ausnahmen behandelt.
Warum wirft JavaScript nicht öfter Ausnahmen?
JavaScript unterstützte bis ES3 keine Ausnahmen. Das erklärt, warum sie von der Sprache und ihrer Standardbibliothek sparsam verwendet werden.
Betrachten Sie den folgenden Code. Er liest Profile, die in Dateien gespeichert sind, in ein Array mit Instanzen der Klasse Profile ein.
function readProfiles(filePaths) {
const profiles = [];
for (const filePath of filePaths) {
try {
const profile = readOneProfile(filePath);
profiles.push(profile);
} catch (err) { // (A)
console.log('Error in: '+filePath, err);
}
}
}
function readOneProfile(filePath) {
const profile = new Profile();
const file = openFile(filePath);
// ··· (Read the data in `file` into `profile`)
return profile;
}
function openFile(filePath) {
if (!fs.existsSync(filePath)) {
throw new Error('Could not find file '+filePath); // (B)
}
// ··· (Open the file whose path is `filePath`)
}Betrachten wir, was in Zeile B passiert: Es ist ein Fehler aufgetreten, aber der beste Ort, um das Problem zu behandeln, ist nicht der aktuelle Ort, sondern Zeile A. Dort können wir die aktuelle Datei überspringen und mit der nächsten fortfahren.
Daher
throw-Anweisung, um anzuzeigen, dass ein Problem aufgetreten ist.try-catch-Anweisung, um das Problem zu behandeln.Wenn wir werfen, sind die folgenden Konstrukte aktiv:
readProfiles(···)
for (const filePath of filePaths)
try
readOneProfile(···)
openFile(···)
if (!fs.existsSync(filePath))
throw
Einzeln beendet throw die verschachtelten Konstrukte, bis es auf eine try-Anweisung stößt. Die Ausführung wird in der catch-Klausel dieser try-Anweisung fortgesetzt.
throwDies ist die Syntax der throw-Anweisung:
throw «value»;Jeder Wert kann in JavaScript geworfen werden. Es ist jedoch am besten, Instanzen von Error oder einer Unterklasse zu verwenden, da diese zusätzliche Funktionen wie Stack-Traces und Fehlerkettenbildung unterstützen (siehe §24.4 „Error und seine Unterklassen“).
Das lässt uns mit den folgenden Optionen:
Verwendung der Klasse Error direkt. Das ist in JavaScript weniger einschränkend als in einer statischeren Sprache, da wir Instanzen eigene Eigenschaften hinzufügen können.
const err = new Error('Could not find the file');
err.filePath = filePath;
throw err;Verwendung einer der Unterklassen von Error.
Unterklassenbildung von Error (mehr Details werden später erläutert).
class MyError extends Error {
}
function func() {
throw new MyError('Problem!');
}
assert.throws(
() => func(),
MyError);try-AnweisungDie maximale Version der try-Anweisung sieht wie folgt aus:
try {
«try_statements»
} catch (error) {
«catch_statements»
} finally {
«finally_statements»
}Wir können diese Klauseln wie folgt kombinieren:
try-catchtry-finallytry-catch-finallytry-BlockDer try-Block kann als Körper der Anweisung betrachtet werden. Hier führen wir den regulären Code aus.
catch-KlauselWenn eine Ausnahme den try-Block erreicht, wird sie dem Parameter der catch-Klausel zugewiesen und der Code in dieser Klausel wird ausgeführt. Danach wird die Ausführung normalerweise nach der try-Anweisung fortgesetzt. Dies kann sich ändern, wenn
return-, break- oder throw-Anweisung innerhalb des catch-Blocks gibt.finally-Klausel gibt (die immer ausgeführt wird, bevor die try-Anweisung endet).Der folgende Code demonstriert, dass der in Zeile A geworfene Wert tatsächlich in Zeile B gefangen wird.
const errorObject = new Error();
function func() {
throw errorObject; // (A)
}
try {
func();
} catch (err) { // (B)
assert.equal(err, errorObject);
}catch-Bindung [ES2019]Wir können den catch-Parameter weglassen, wenn wir an dem geworfenen Wert nicht interessiert sind.
try {
// ···
} catch {
// ···
}Das kann gelegentlich nützlich sein. Zum Beispiel hat Node.js die API-Funktion assert.throws(func), die prüft, ob innerhalb von func ein Fehler geworfen wird. Sie könnte wie folgt implementiert werden.
function throws(func) {
try {
func();
} catch {
return; // everything OK
}
throw new Error('Function didn’t throw an exception!');
}Eine vollständigere Implementierung dieser Funktion hätte jedoch einen catch-Parameter und würde beispielsweise prüfen, ob sein Typ wie erwartet ist.
finally-KlauselDer Code innerhalb der finally-Klausel wird am Ende einer try-Anweisung immer ausgeführt – unabhängig davon, was im try-Block oder in der catch-Klausel passiert.
Schauen wir uns einen häufigen Anwendungsfall für finally an: Wir haben eine Ressource erstellt und möchten sie immer zerstören, wenn wir damit fertig sind, egal was während der Arbeit damit passiert. Wir würden das wie folgt implementieren:
const resource = createResource();
try {
// Work with `resource`. Errors may be thrown.
} finally {
resource.destroy();
}finally wird immer ausgeführtDie finally-Klausel wird immer ausgeführt, auch wenn ein Fehler geworfen wird (Zeile A).
let finallyWasExecuted = false;
assert.throws(
() => {
try {
throw new Error(); // (A)
} finally {
finallyWasExecuted = true;
}
},
Error
);
assert.equal(finallyWasExecuted, true);Und auch wenn es eine return-Anweisung gibt (Zeile A).
let finallyWasExecuted = false;
function func() {
try {
return; // (A)
} finally {
finallyWasExecuted = true;
}
}
func();
assert.equal(finallyWasExecuted, true);Error und seine UnterklassenError ist die gemeinsame Oberklasse aller integrierten Fehlerklassen.
ErrorSo sehen die Instanz-Eigenschaften und der Konstruktor von Error aus:
class Error {
// Instance properties
message: string;
cause?: any; // ES2022
stack: string; // non-standard but widely supported
constructor(
message: string = '',
options?: ErrorOptions // ES2022
);
}
interface ErrorOptions {
cause?: any; // ES2022
}Der Konstruktor hat zwei Parameter:
message gibt eine Fehlermeldung an.options wurde in ECMAScript 2022 eingeführt. Es enthält ein Objekt, bei dem eine Eigenschaft derzeit unterstützt wird:.cause gibt an, welche Ausnahme (falls vorhanden) den aktuellen Fehler verursacht hat.Die nachfolgenden Unterabschnitte erklären die Instanz-Eigenschaften .message, .cause und .stack detaillierter.
Error.prototype.nameJede integrierte Fehlerklasse E hat eine Eigenschaft E.prototype.name.
> Error.prototype.name
'Error'
> RangeError.prototype.name
'RangeError'Daher gibt es zwei Möglichkeiten, den Namen der Klasse eines integrierten Fehlerobjekts zu erhalten:
> new RangeError().name
'RangeError'
> new RangeError().constructor.name
'RangeError'.message von Error.message enthält nur die Fehlermeldung.
const err = new Error('Hello!');
assert.equal(String(err), 'Error: Hello!');
assert.equal(err.message, 'Hello!');Wenn wir die Nachricht weglassen, wird der leere String als Standardwert verwendet (geerbt von Error.prototype.message).
Wenn wir die Nachricht weglassen, ist es der leere String.
assert.equal(new Error().message, '');.stack von ErrorDie Instanz-Eigenschaft .stack ist keine ECMAScript-Funktion, wird aber von JavaScript-Engines weitgehend unterstützt. Sie ist normalerweise ein String, aber ihre genaue Struktur ist nicht standardisiert und variiert zwischen den Engines.
So sieht sie auf der JavaScript-Engine V8 aus:
const err = new Error('Hello!');
assert.equal(
err.stack,
`
Error: Hello!
at file://ch_exception-handling.mjs:1:13
`.trim());.cause von Error [ES2022]Die Instanz-Eigenschaft .cause wird über das Options-Objekt im zweiten Parameter von new Error() erstellt. Sie gibt an, welcher andere Fehler den aktuellen verursacht hat.
const err = new Error('msg', {cause: 'the cause'});
assert.equal(err.cause, 'the cause');Informationen zur Verwendung dieser Eigenschaft finden Sie unter §24.5 „Fehlerkettenbildung“.
ErrorError hat die folgenden Unterklassen – zitiert aus der ECMAScript-Spezifikation:
AggregateError [ES2021] repräsentiert mehrere Fehler gleichzeitig. In der Standardbibliothek wird nur Promise.any() verwendet.RangeError gibt an, dass ein Wert nicht in der Menge oder dem Bereich zulässiger Werte liegt.ReferenceError gibt an, dass ein ungültiger Referenzwert erkannt wurde.SyntaxError gibt an, dass ein Parsing-Fehler aufgetreten ist.TypeError wird verwendet, um einen fehlgeschlagenen Vorgang anzuzeigen, wenn keiner der anderen *NativeError*-Objekte eine geeignete Angabe für die Fehlerursache darstellt.URIError gibt an, dass eine der globalen URI-Handling-Funktionen auf eine Weise verwendet wurde, die mit ihrer Definition unvereinbar ist.ErrorSeit ECMAScript 2022 akzeptiert der Error-Konstruktor zwei Parameter (siehe vorherige Unterabschnitt). Daher haben wir beim Unterklassenbilden zwei Möglichkeiten: Wir können entweder den Konstruktor in unserer Unterklasse weglassen oder super() wie folgt aufrufen:
class MyCustomError extends Error {
constructor(message, options) {
super(message, options);
// ···
}
}Manchmal fangen wir Fehler ab, die während eines tiefer verschachtelten Funktionsaufrufs geworfen werden, und möchten zusätzliche Informationen anhängen.
function readFiles(filePaths) {
return filePaths.map(
(filePath) => {
try {
const text = readText(filePath);
const json = JSON.parse(text);
return processJson(json);
} catch (error) {
// (A)
}
});
}Die Anweisungen innerhalb der try-Klausel können verschiedenste Fehler werfen. In den meisten Fällen wird ein Fehler den Pfad der Datei, die ihn verursacht hat, nicht kennen. Deshalb möchten wir diese Informationen in Zeile A anhängen.
error.cause [ES2022]Seit ECMAScript 2022 ermöglicht new Error(), anzugeben, was ihn verursacht hat.
function readFiles(filePaths) {
return filePaths.map(
(filePath) => {
try {
// ···
} catch (error) {
throw new Error(
`While processing ${filePath}`,
{cause: error}
);
}
});
}.cause: eine benutzerdefinierte FehlerklasseDie folgende benutzerdefinierte Fehlerklasse unterstützt Fehlerkettenbildung. Sie ist vorwärtskompatibel mit .cause.
/**
* An error class that supports error chaining.
* If there is built-in support for .cause, it uses it.
* Otherwise, it creates this property itself.
*
* @see https://github.com/tc39/proposal-error-cause
*/
class CausedError extends Error {
constructor(message, options) {
super(message, options);
if (
(isObject(options) && 'cause' in options)
&& !('cause' in this)
) {
// .cause was specified but the superconstructor
// did not create an instance property.
const cause = options.cause;
this.cause = cause;
if ('stack' in cause) {
this.stack = this.stack + '\nCAUSE: ' + cause.stack;
}
}
}
}
function isObject(value) {
return value !== null && typeof value === 'object';
} Übung: Ausnahmebehandlung
exercises/exception-handling/call_function_test.mjs
Quiz
Siehe Quiz-App.