Kapitel 14. Fehlerbehandlung
Inhaltsverzeichnis
Das Buch kaufen
(Werbung, bitte nicht blockieren.)

Kapitel 14. Fehlerbehandlung

Dieses Kapitel beschreibt, wie die Fehlerbehandlung in JavaScript funktioniert. Es beginnt mit einer allgemeinen Erklärung, was Fehlerbehandlung ist.

Was ist Fehlerbehandlung?

Bei der Fehlerbehandlung gruppieren Sie oft eng miteinander verbundene Anweisungen. Wenn eine dieser Anweisungen während der Ausführung einen Fehler verursacht, macht es keinen Sinn, mit den restlichen Anweisungen fortzufahren. Stattdessen versuchen Sie, den Fehler so gut wie möglich zu beheben. Dies erinnert lose an Transaktionen (aber ohne Atomizität).

Sehen wir uns Code ohne Fehlerbehandlung an

function processFiles() {
    var fileNames = collectFileNames();
    var entries = extractAllEntries(fileNames);
    processEntries(entries);
}
function extractAllEntries(fileNames) {
    var allEntries = new Entries();
    fileNames.forEach(function (fileName) {
        var entry = extractOneEntry(fileName);
        allEntries.add(entry);  // (1)
    });
}
function extractOneEntry(fileName) {
    var file = openFile(fileName);  // (2)
    ...
}
...

Was ist der beste Weg, auf einen Fehler in openFile() bei (2) zu reagieren? Offensichtlich sollte die Anweisung (1) nicht mehr ausgeführt werden. Aber wir wollen extractAllEntries() auch nicht abbrechen. Stattdessen reicht es aus, die aktuelle Datei zu überspringen und mit der nächsten fortzufahren. Um dies zu tun, fügen wir der vorherigen Code Fehlerbehandlung hinzu.

function extractAllEntries(fileNames) {
    var allEntries = new Entries();
    fileNames.forEach(function (fileName) {
        try {
            var entry = extractOneEntry(fileName);
            allEntries.add(entry);
        } catch (exception) {  // (2)
            errorLog.log('Error in '+fileName, exception);
        }
    });
}
function extractOneEntry(fileName) {
    var file = openFile(fileName);
    ...
}
function openFile(fileName) {
    if (!exists(fileName)) {
        throw new Error('Could not find file '+fileName); // (1)
    }
    ...
}

Es gibt zwei Aspekte der Fehlerbehandlung

  1. Wenn ein Problem auftritt, das nicht sinnvoll an seiner Entstehungsstelle behandelt werden kann, werfen Sie eine Ausnahme.
  2. Finden Sie eine Stelle, an der Fehler behandelt werden können: Fangen Sie Ausnahmen ab.

Bei (1) sind die folgenden Konstrukte aktiv

    processFile()
        extractAllEntries(...)
            fileNames.forEach(...)
                function (fileName) { ... }
                    try { ... } catch (exception) { ... }
                        extractOneEntry(...)
                            openFile(...)

Die throw-Anweisung bei (1) durchläuft diesen Baum und verlässt alle Konstrukte, bis sie auf eine aktive try-Anweisung stößt. Dann ruft sie den catch-Block dieser Anweisung auf und übergibt ihm den Ausnahmewert.

Fehlerbehandlung in JavaScript

Die Fehlerbehandlung in JavaScript funktioniert wie in den meisten Programmiersprachen: Eine try-Anweisung gruppiert Anweisungen und ermöglicht es Ihnen, Ausnahmen in diesen Anweisungen abzufangen.

throw

Die Syntax von throw ist wie folgt:

throw «value»;

Jeder JavaScript-Wert kann geworfen werden. Der Einfachheit halber werfen viele JavaScript-Programme nur Zeichenketten.

// Don't do this
if (somethingBadHappened) {
    throw 'Something bad happened';
}

Tun Sie das nicht. JavaScript verfügt über spezielle Konstruktoren für Fehlerobjekte (siehe Error Constructors). Verwenden Sie diese oder leiten Sie sie davon ab (siehe Kapitel 28). Ihr Vorteil ist, dass JavaScript automatisch einen Stack-Trace hinzufügt (auf den meisten Engines) und dass sie Platz für zusätzliche kontextspezifische Eigenschaften bieten. Die einfachste Lösung ist die Verwendung des integrierten Konstruktors Error().

if (somethingBadHappened) {
    throw new Error('Something bad happened');
}

try-catch-finally

Die Syntax von try-catch-finally sieht wie folgt aus. try ist obligatorisch, und mindestens einer der catch und finally muss ebenfalls vorhanden sein:

try {
    «try_statements»
}
catch («exceptionVar») {
   «catch_statements»
}
finally {
   «finally_statements»
}

So funktioniert es

  • catch fängt jede Ausnahme ab, die in try_statements geworfen wird, sei es direkt oder in von ihnen aufgerufenen Funktionen. Tipp: Wenn Sie zwischen verschiedenen Arten von Ausnahmen unterscheiden möchten, können Sie die constructor-Eigenschaft verwenden, um über die Konstruktoren der Ausnahmen zu schalten (siehe Anwendungsfälle für die constructor-Eigenschaft).
  • finally wird immer ausgeführt, unabhängig davon, was in try_statements (oder in von ihnen aufgerufenen Funktionen) geschieht. Verwenden Sie es für Bereinigungsoperationen, die immer ausgeführt werden sollten, egal was in try_statements passiert:

    var resource = allocateResource();
    try {
        ...
    } finally {
        resource.deallocate();
    }

    Wenn eine der try_statements eine return ist, wird der finally-Block danach ausgeführt (unmittelbar vor dem Verlassen der Funktion oder Methode; siehe die folgenden Beispiele).

Beispiele

Jeder Wert kann geworfen werden:

function throwIt(exception) {
    try {
        throw exception;
    } catch (e) {
        console.log('Caught: '+e);
    }
}

Hier ist die Interaktion

> throwIt(3);
Caught: 3
> throwIt('hello');
Caught: hello
> throwIt(new Error('An error happened'));
Caught: Error: An error happened

finally wird immer ausgeführt

function throwsError() {
    throw new Error('Sorry...');
}
function cleansUp() {
    try {
        throwsError();
    } finally {
        console.log('Performing clean-up');
    }
}

Hier ist die Interaktion

> cleansUp();
Performing clean-up
Error: Sorry...

finally wird nach einer return-Anweisung ausgeführt:

function idLog(x) {
    try {
        console.log(x);
        return 'result';
    } finally {
        console.log("FINALLY");
    }
}

Hier ist die Interaktion

> idLog('arg')
arg
FINALLY
'result'

Der Rückgabewert wird vor der Ausführung von finally in die Warteschlange gestellt.

var count = 0;
function countUp() {
    try {
        return count;
    } finally {
        count++;  // (1)
    }
}

Zu dem Zeitpunkt, an dem Anweisung (1) ausgeführt wird, wurde der Wert von count bereits für die Rückgabe in die Warteschlange gestellt.

> countUp()
0
> count
1

Error-Konstruktoren

ECMAScript standardisiert die folgenden Fehlerkonstruktoren. Die Beschreibungen sind aus der ECMAScript 5-Spezifikation zitiert:

  • Error ist ein generischer Konstruktor für Fehler. Alle anderen hier genannten Fehlerkonstruktoren sind Unterkonstruktoren.
  • EvalError „wird derzeit in dieser Spezifikation nicht verwendet. Dieses Objekt bleibt aus Kompatibilitätsgründen mit früheren Ausgaben dieser Spezifikation bestehen.“
  • RangeError „zeigt an, dass ein numerischer Wert den zulässigen Bereich überschritten hat.“ Zum Beispiel:

    > new Array(-1)
    RangeError: Invalid array length
  • ReferenceError „zeigt an, dass ein ungültiger Referenzwert erkannt wurde.“ Dies ist normalerweise eine unbekannte Variable. Zum Beispiel:

    > unknownVariable
    ReferenceError: unknownVariable is not defined
  • SyntaxError „zeigt an, dass ein Parsenfehler aufgetreten ist“, entweder beim Parsen von normalem Code oder beim Parsen des Arguments von eval(). Zum Beispiel:

    > 3..1
    SyntaxError: Unexpected number '.1'. Parse error.
    > eval('5 +')
    SyntaxError: Unexpected end of script
  • TypeError „zeigt an, dass der tatsächliche Typ eines Operanden anders ist als der erwartete Typ.“ Zum Beispiel:

    > undefined.foo
    TypeError: Cannot read property 'foo' of undefined
  • URIError „zeigt an, dass eine der globalen URI-Handlungsfunktionen auf eine Weise verwendet wurde, die mit ihrer Definition unvereinbar ist.“ Zum Beispiel:

    > decodeURI('%2')
    URIError: URI malformed

Hier sind die Eigenschaften von Fehlern:

message
Die Fehlermeldung.
name
Der Name des Fehlers.
stack
Ein Stack-Trace. Dies ist nicht standardisiert, aber auf vielen Plattformen verfügbar – z. B. Chrome, Node.js und Firefox.

Stack-Traces

Die üblichen Fehlerquellen sind entweder extern (falsche Eingabe, fehlende Datei usw.) oder intern (ein Fehler im Programm). Insbesondere im letzteren Fall treten unerwartete Ausnahmen auf und Sie müssen debuggen. Oft haben Sie keinen laufenden Debugger. Für das „manuelle“ Debugging sind zwei Informationen hilfreich:

  1. Daten: Welche Werte haben Variablen?
  2. Ausführung: In welcher Zeile ist die Ausnahme aufgetreten und welche Funktionsaufrufe waren aktiv?

Sie können einen Teil des ersten Punkts (Daten) entweder in die Meldung oder in die Eigenschaften eines Fehlerobjekts einfügen. Der zweite Punkt (Ausführung) wird auf vielen JavaScript-Engines über Stack-Traces unterstützt, Schnappschüsse des Aufrufstapels, als die Fehlerobjekte erstellt wurden. Das folgende Beispiel gibt einen Stack-Trace aus:

function catchIt() {
    try {
        throwIt();
    } catch (e) {
        console.log(e.stack); // print stack trace
    }
}
function throwIt() {
    throw new Error('');
}

Hier ist die Interaktion

> catchIt()
Error
    at throwIt (~/examples/throwcatch.js:9:11)
    at catchIt (~/examples/throwcatch.js:3:9)
    at repl:1:5

Implementieren Ihres eigenen Fehlerkonstruktors

Wenn Sie Stack-Traces wünschen, benötigen Sie die Dienste der integrierten Fehlerkonstruktoren. Sie können einen vorhandenen Konstruktor verwenden und Ihre eigenen Daten daran anhängen. Oder Sie können einen Unterkonstruktor erstellen, dessen Instanzen sich über instanceof von denen anderer Fehlerkonstruktoren unterscheiden lassen. Leider ist dies (für integrierte Konstruktoren) kompliziert; siehe Kapitel 28, um zu erfahren, wie dies geht.

Weiter: 15. Funktionen