Bessere Fehlerbehandlung in NodeJS mit Fehlerklassen
Veröffentlicht: 2022-03-10error
und wie Sie es für eine bessere und effizientere Art der Fehlerbehandlung in Ihren Anwendungen verwenden können.Die Fehlerbehandlung ist einer der Teile der Softwareentwicklung, die nicht ganz die Aufmerksamkeit erhalten, die sie wirklich verdienen. Das Erstellen robuster Anwendungen erfordert jedoch den richtigen Umgang mit Fehlern.
Sie können in NodeJS auskommen, ohne Fehler richtig zu behandeln, aber aufgrund der asynchronen Natur von NodeJS können unsachgemäße Behandlung oder Fehler Ihnen schon bald Schmerzen bereiten – insbesondere beim Debuggen von Anwendungen.
Bevor wir fortfahren, möchte ich auf die Art von Fehlern hinweisen, die wir besprechen werden, wie man Fehlerklassen verwendet.
Betriebsfehler
Dies sind Fehler, die während der Laufzeit eines Programms entdeckt werden. Betriebsfehler sind keine Fehler und können von Zeit zu Zeit auftreten, hauptsächlich aufgrund eines oder einer Kombination mehrerer externer Faktoren, wie z.
Nachfolgend finden Sie weitere Beispiele für Betriebsfehler:
- Verbindung zu einem Datenbankserver fehlgeschlagen;
- Ungültige Eingaben des Benutzers (Server antwortet mit einem
400
-Antwortcode); - Zeitüberschreitung der Anforderung;
- Ressource nicht gefunden (Server antwortet mit Antwortcode 404);
- Der Server kehrt mit einer
500
-Antwort zurück.
Es ist auch erwähnenswert, kurz auf das Gegenstück zu Operational Errors einzugehen.
Programmierfehler
Dies sind Fehler im Programm, die durch Ändern des Codes behoben werden können. Diese Arten von Fehlern können nicht behandelt werden, da sie als Ergebnis eines beschädigten Codes auftreten. Beispiele für diese Fehler sind:
- Versuch, eine Eigenschaft eines nicht definierten Objekts zu lesen.
const user = { firstName: 'Kelvin', lastName: 'Omereshone', } console.log(user.fullName) // throws 'undefined' because the property fullName is not defined
- Aufrufen oder Aufrufen einer asynchronen Funktion ohne Rückruf.
- Übergeben einer Zeichenfolge, wo eine Zahl erwartet wurde.
Dieser Artikel befasst sich mit der Behandlung von Betriebsfehlern in NodeJS. Die Fehlerbehandlung in NodeJS unterscheidet sich erheblich von der Fehlerbehandlung in anderen Sprachen. Dies liegt an der asynchronen Natur von JavaScript und der Offenheit von JavaScript mit Fehlern. Lassen Sie mich erklären:
In JavaScript können Sie nicht nur Instanzen der error
werfen. Sie können buchstäblich jeden Datentyp werfen, den diese Offenheit in anderen Sprachen nicht zulässt.
Beispielsweise kann ein JavaScript-Entwickler entscheiden, statt einer Fehlerobjektinstanz eine Zahl einzufügen, etwa so:
// bad throw 'Whoops :)'; // good throw new Error('Whoops :)')
Möglicherweise sehen Sie das Problem beim Auslösen anderer Datentypen nicht, aber das Debuggen wird dadurch schwieriger, da Sie keinen Stack-Trace und andere Eigenschaften erhalten, die das Error-Objekt verfügbar macht und die zum Debuggen benötigt werden.
Schauen wir uns einige falsche Muster bei der Fehlerbehandlung an, bevor wir uns das Muster der Error-Klasse ansehen und erklären, wie es eine viel bessere Methode zur Fehlerbehandlung in NodeJS ist.
Schlechtes Fehlerbehandlungsmuster Nr. 1: Falsche Verwendung von Rückrufen
Reales Szenario : Ihr Code hängt von einer externen API ab, die einen Rückruf erfordert, um das erwartete Ergebnis zu erhalten.
Nehmen wir das folgende Code-Snippet:
'use strict'; const fs = require('fs'); const write = function () { fs.mkdir('./writeFolder'); fs.writeFile('./writeFolder/foobar.txt', 'Hello World'); } write();
Bis NodeJS 8 und höher war der obige Code legitim, und Entwickler feuerten einfach Befehle ab und vergaßen sie. Das bedeutet, dass Entwickler keinen Rückruf für solche Funktionsaufrufe bereitstellen mussten und daher die Fehlerbehandlung weglassen konnten. Was passiert, wenn der writeFolder
nicht erstellt wurde? Der Aufruf von writeFile
wird nicht gemacht und wir würden nichts darüber wissen. Dies kann auch zu einer Race-Bedingung führen, da der erste Befehl möglicherweise noch nicht beendet wurde, als der zweite Befehl erneut gestartet wurde, Sie würden es nicht wissen.
Beginnen wir mit der Lösung dieses Problems, indem wir die Race Condition lösen. Wir würden dies tun, indem wir dem ersten Befehl mkdir
einen Rückruf geben, um sicherzustellen, dass das Verzeichnis tatsächlich existiert, bevor wir mit dem zweiten Befehl darauf schreiben. Unser Code würde also wie folgt aussehen:
'use strict'; const fs = require('fs'); const write = function () { fs.mkdir('./writeFolder', () => { fs.writeFile('./writeFolder/foobar.txt', 'Hello World!'); }); } write();
Obwohl wir die Race Condition gelöst haben, sind wir noch nicht ganz fertig. Unser Code ist immer noch problematisch, denn obwohl wir für den ersten Befehl einen Rückruf verwendet haben, haben wir keine Möglichkeit zu wissen, ob der Ordner writeFolder
erstellt wurde oder nicht. Wenn der Ordner nicht erstellt wurde, schlägt der zweite Aufruf erneut fehl, aber trotzdem haben wir den Fehler erneut ignoriert. Wir lösen das, indem wir …
Fehlerbehandlung mit Callbacks
Um Fehler mit Rückrufen richtig zu behandeln, müssen Sie sicherstellen, dass Sie immer den Fehler-zuerst-Ansatz verwenden. Dies bedeutet, dass Sie zuerst prüfen sollten, ob von der Funktion ein Fehler zurückgegeben wird, bevor Sie die zurückgegebenen Daten (falls vorhanden) verwenden. Mal sehen, wie man das falsch macht:
'use strict'; // Wrong const fs = require('fs'); const write = function (callback) { fs.mkdir('./writeFolder', (err, data) => { if (data) fs.writeFile('./writeFolder/foobar.txt', 'Hello World!'); else callback(err) }); } write(console.log);
Das obige Muster ist falsch, da die von Ihnen aufgerufene API manchmal keinen Wert oder einen falschen Wert als gültigen Rückgabewert zurückgibt. Dies würde dazu führen, dass Sie in einem Fehlerfall landen, obwohl Sie möglicherweise einen erfolgreichen Aufruf der Funktion oder API haben.
Das obige Muster ist auch schlecht, weil seine Verwendung Ihren Fehler auffressen würde (Ihre Fehler werden nicht aufgerufen, obwohl es passiert sein könnte). Sie haben auch keine Ahnung, was in Ihrem Code als Ergebnis dieser Art von Fehlerbehandlungsmustern passiert. Der richtige Weg für den obigen Code wäre also:
'use strict'; // Right const fs = require('fs'); const write = function (callback) { fs.mkdir('./writeFolder', (err, data) => { if (err) return callback(err) fs.writeFile('./writeFolder/foobar.txt', 'Hello World!'); }); } write(console.log);
Falsches Fehlerbehandlungsmuster Nr. 2: Falsche Verwendung von Versprechen
Szenario aus der realen Welt : Sie haben Promises entdeckt und denken, dass sie aufgrund der Callback-Hölle viel besser sind als Rückrufe, und Sie haben sich entschieden, eine externe API zu versprechen, von der Ihre Codebasis abhängt. Oder Sie verbrauchen ein Versprechen von einer externen API oder einer Browser-API wie der Funktion fetch().
Heutzutage verwenden wir Callbacks nicht wirklich in unseren NodeJS-Codebasen, wir verwenden Promises. Lassen Sie uns also unseren Beispielcode mit einem Versprechen neu implementieren:
'use strict'; const fs = require('fs').promises; const write = function () { return fs.mkdir('./writeFolder').then(() => { fs.writeFile('./writeFolder/foobar.txt', 'Hello world!') }).catch((err) => { // catch all potential errors console.error(err) }) }
Lassen Sie uns den obigen Code unter die Lupe nehmen – wir können sehen, dass wir das fs.mkdir
in eine andere Promise-Kette (den Aufruf von fs.writeFile) verzweigen, ohne diesen Promise-Aufruf überhaupt zu verarbeiten. Sie könnten denken, ein besserer Weg, es zu tun, wäre:
'use strict'; const fs = require('fs').promises; const write = function () { return fs.mkdir('./writeFolder').then(() => { fs.writeFile('./writeFolder/foobar.txt', 'Hello world!').then(() => { // do something }).catch((err) => { console.error(err); }) }).catch((err) => { // catch all potential errors console.error(err) }) }
Aber das obige würde nicht skalieren. Das liegt daran, dass wir, wenn wir mehr Promise-Ketten zum Callen hätten, mit etwas Ähnlichem wie der Callback-Hölle enden würden, für deren Lösung Versprechungen gemacht wurden. Das bedeutet, dass unser Code weiter nach rechts einrückt. Wir hätten eine versprochene Hölle auf unseren Händen.
Versprechen einer Callback-basierten API
Meistens möchten Sie selbst eine Callback-basierte API versprechen, um Fehler auf dieser API besser zu behandeln. Dies ist jedoch nicht wirklich einfach zu bewerkstelligen. Nehmen wir unten ein Beispiel, um zu erklären, warum.
function doesWillNotAlwaysSettle(arg) { return new Promise((resolve, reject) => { doATask(foo, (err) => { if (err) { return reject(err); } if (arg === true) { resolve('I am Done') } }); }); }
Wenn arg
nicht true
ist und wir keinen Fehler vom Aufruf der doATask
Funktion haben, bleibt dieses Versprechen einfach hängen, was ein Speicherleck in Ihrer Anwendung ist.
Verschluckte Synchronisierungsfehler in Versprechungen
Die Verwendung des Promise-Konstruktors hat mehrere Schwierigkeiten. Eine dieser Schwierigkeiten ist; Sobald es entweder gelöst oder abgelehnt wurde, kann es keinen anderen Zustand annehmen. Dies liegt daran, dass ein Promise nur einen einzigen Zustand annehmen kann – entweder es steht aus oder es wird aufgelöst/abgelehnt. Das bedeutet, dass wir tote Zonen in unseren Versprechen haben können. Sehen wir uns das im Code an:
function deadZonePromise(arg) { return new Promise((resolve, reject) => { doATask(foo, (err) => { resolve('I'm all Done'); throw new Error('I am never reached') // Dead Zone }); }); }
Aus dem Obigen sehen wir, sobald das Versprechen aufgelöst ist, ist die nächste Zeile eine tote Zone und wird niemals erreicht. Dies bedeutet, dass alle folgenden synchronen Fehlerbehandlungsvorgänge in Ihren Versprechungen einfach geschluckt und niemals ausgelöst werden.
Beispiele aus der Praxis
Die obigen Beispiele helfen, schlechte Fehlerbehandlungsmuster zu erklären, werfen wir einen Blick auf die Art von Problemen, die Sie im wirklichen Leben sehen könnten.
Beispiel 1 aus der realen Welt – Fehler in Zeichenfolge umwandeln
Szenario : Sie haben entschieden, dass der von einer API zurückgegebene Fehler nicht wirklich gut genug für Sie ist, also haben Sie sich entschieden, Ihre eigene Nachricht hinzuzufügen.
'use strict'; function readTemplate() { return new Promise(() => { databaseGet('query', function(err, data) { if (err) { reject('Template not found. Error: ', + err); } else { resolve(data); } }); }); } readTemplate();
Schauen wir uns an, was mit dem obigen Code falsch ist. Aus dem Obigen sehen wir, dass der Entwickler versucht, den von der databaseGet
-API ausgelösten Fehler zu verbessern, indem er den zurückgegebenen Fehler mit der Zeichenfolge „Vorlage nicht gefunden“ verkettet. Dieser Ansatz hat viele Nachteile, da der Entwickler nach der Verkettung implizit toString
für das zurückgegebene Fehlerobjekt ausführt. Auf diese Weise verliert er alle zusätzlichen Informationen, die durch den Fehler zurückgegeben werden (verabschieden Sie sich vom Stack-Trace). Was der Entwickler also gerade hat, ist nur eine Zeichenfolge, die beim Debuggen nicht nützlich ist.
Eine bessere Möglichkeit besteht darin, den Fehler so zu lassen, wie er ist, oder ihn in einen anderen Fehler einzuschließen, den Sie erstellt haben, und den ausgelösten Fehler aus dem databaseGet-Aufruf als Eigenschaft daran anzuhängen.
Beispiel Nr. 2 aus der Praxis: Den Fehler vollständig ignorieren
Szenario : Wenn sich ein Benutzer in Ihrer Anwendung anmeldet und ein Fehler auftritt, möchten Sie vielleicht nur den Fehler abfangen und eine benutzerdefinierte Meldung anzeigen, aber Sie haben den abgefangenen Fehler vollständig ignoriert, ohne ihn überhaupt zu Debugging-Zwecken zu protokollieren.
router.get('/:id', function (req, res, next) { database.getData(req.params.userId) .then(function (data) { if (data.length) { res.status(200).json(data); } else { res.status(404).end(); } }) .catch(() => { log.error('db.rest/get: could not get data: ', req.params.userId); res.status(500).json({error: 'Internal server error'}); }) });
Aus dem Obigen können wir ersehen, dass der Fehler vollständig ignoriert wird und der Code 500 an den Benutzer sendet, wenn der Aufruf der Datenbank fehlgeschlagen ist. Aber in Wirklichkeit könnte die Ursache für den Datenbankfehler fehlerhafte Daten sein, die vom Benutzer gesendet wurden, was ein Fehler mit dem Statuscode 400 ist.
Im obigen Fall würden wir in einem Debugging-Horror enden, weil Sie als Entwickler nicht wissen würden, was schief gelaufen ist. Der Benutzer kann keinen anständigen Bericht erstellen, da immer ein interner Serverfehler 500 ausgegeben wird. Sie würden am Ende Stunden damit verschwenden, das Problem zu finden, was einer Verschwendung von Zeit und Geld Ihres Arbeitgebers gleichkommt.
Beispiel Nr. 3 aus der Praxis: Den von einer API ausgegebenen Fehler nicht akzeptieren
Szenario : Ein Fehler wurde von einer API ausgegeben, die Sie verwendet haben, aber Sie akzeptieren diesen Fehler nicht, stattdessen marshallten und transformieren Sie den Fehler so, dass er für Debugging-Zwecke unbrauchbar wird.
Nehmen Sie das folgende Codebeispiel unten:
async function doThings(input) { try { validate(input); try { await db.create(input); } catch (error) { error.message = `Inner error: ${error.message}` if (error instanceof Klass) { error.isKlass = true; } throw error } } catch (error) { error.message = `Could not do things: ${error.message}`; await rollback(input); throw error; } }
Im obigen Code passiert eine Menge, was zu Debugging-Horror führen würde. Lass uns einen Blick darauf werfen:
-
try/catch
-Blöcke umschließen: Oben sehen Sie, dass wirtry/catch
/Catch-Blöcke umschließen, was eine sehr schlechte Idee ist. Normalerweise versuchen wir, die Verwendung vontry/catch
-Blöcken zu reduzieren, um die Oberfläche zu minimieren, auf der wir unseren Fehler behandeln müssten (stellen Sie sich das als DRY-Fehlerbehandlung vor); - Wir manipulieren auch die Fehlermeldung, um sie zu verbessern, was ebenfalls keine gute Idee ist;
- Wir prüfen, ob der Fehler eine Instanz vom Typ
Klass
ist, und in diesem Fall setzen wir eine boolesche Eigenschaft des FehlersisKlass
auf truev (aber wenn diese Prüfung bestanden wird, ist der Fehler vom TypKlass
); - Wir setzen die Datenbank auch zu früh zurück, da aufgrund der Codestruktur eine hohe Tendenz besteht, dass wir die Datenbank möglicherweise nicht einmal getroffen haben, als der Fehler ausgelöst wurde.
Unten ist eine bessere Möglichkeit, den obigen Code zu schreiben:
async function doThings(input) { validate(input); try { await db.create(input); } catch (error) { try { await rollback(); } catch (error) { logger.log('Rollback failed', error, 'input:', input); } throw error; } }
Lassen Sie uns analysieren, was wir im obigen Snippet richtig machen:
- Wir verwenden einen
try/catch
-Block und nur im Catch-Block verwenden wir einen weiterentry/catch
-Block, der als Wächter dienen soll, falls etwas mit dieser Rollback-Funktion weitergeht und wir das protokollieren; - Schließlich werfen wir unseren ursprünglich empfangenen Fehler aus, was bedeutet, dass wir die in diesem Fehler enthaltene Nachricht nicht verlieren.
Testen
Wir möchten hauptsächlich unseren Code testen (entweder manuell oder automatisch). Aber meistens testen wir nur auf die positiven Dinge. Für einen robusten Test müssen Sie auch auf Fehler und Grenzfälle testen. Diese Nachlässigkeit ist dafür verantwortlich, dass Fehler ihren Weg in die Produktion finden, was mehr zusätzliche Debugging-Zeit kosten würde.
Tipp : Stellen Sie immer sicher, dass Sie nicht nur die positiven Dinge testen (einen Statuscode von 200 von einem Endpunkt erhalten), sondern auch alle Fehlerfälle und alle Grenzfälle.
Beispiel Nr. 4 aus der Praxis: Unbearbeitete Ablehnungen
Wenn Sie bereits Zusagen verwendet haben, sind Sie wahrscheinlich auf unhandled rejections
.
Hier ist eine kurze Einführung zu unbehandelten Ablehnungen. Unbehandelte Ablehnungen sind nicht behandelte Zusagen-Ablehnungen. Das bedeutet, dass das Versprechen abgelehnt wurde, Ihr Code jedoch weiter ausgeführt wird.
Schauen wir uns ein gängiges Beispiel aus der Praxis an, das zu unbehandelten Ablehnungen führt.
'use strict'; async function foobar() { throw new Error('foobar'); } async function baz() { throw new Error('baz') } (async function doThings() { const a = foobar(); const b = baz(); try { await a; await b; } catch (error) { // ignore all errors! } })();
Der obige Code scheint auf den ersten Blick nicht fehleranfällig zu sein. Aber bei genauerem Hinsehen beginnt man einen Mangel zu erkennen. Lassen Sie mich erklären: Was passiert, wenn a
abgelehnt wird? Das bedeutet, dass await b
nie erreicht wird, und das bedeutet, dass es sich um eine unbehandelte Ablehnung handelt. Eine mögliche Lösung besteht darin, Promise.all
für beide Versprechen zu verwenden. Der Code würde also so lauten:
'use strict'; async function foobar() { throw new Error('foobar'); } async function baz() { throw new Error('baz') } (async function doThings() { const a = foobar(); const b = baz(); try { await Promise.all([a, b]); } catch (error) { // ignore all errors! } })();
Hier ist ein weiteres Szenario aus der realen Welt, das zu einem unbehandelten Ablehnungsfehler führen würde:
'use strict'; async function foobar() { throw new Error('foobar'); } async function doThings() { try { return foobar() } catch { // ignoring errors again ! } } doThings();
Wenn Sie das obige Code-Snippet ausführen, erhalten Sie eine unbehandelte Versprechensablehnung, und hier ist der Grund: Obwohl es nicht offensichtlich ist, geben wir ein Versprechen (foobar) zurück, bevor wir es mit try/catch
behandeln. Was wir tun sollten, ist auf das Versprechen zu warten, das wir mit try/catch
handhaben, damit der Code lautet:
'use strict'; async function foobar() { throw new Error('foobar'); } async function doThings() { try { return await foobar() } catch { // ignoring errors again ! } } doThings();
Die negativen Dinge zusammenfassen
Nachdem Sie nun falsche Fehlerbehandlungsmuster und mögliche Korrekturen gesehen haben, tauchen wir nun in das Error-Klassenmuster ein und wie es das Problem der falschen Fehlerbehandlung in NodeJS löst.
Fehlerklassen
In diesem Muster würden wir unsere Anwendung mit einer ApplicationError
-Klasse starten, damit wir wissen, dass alle Fehler in unseren Anwendungen, die wir explizit auslösen, von ihr erben werden. Wir würden also mit den folgenden Fehlerklassen beginnen:
-
ApplicationError
Dies ist der Vorfahre aller anderen Fehlerklassen, dh alle anderen Fehlerklassen erben davon. -
DatabaseError
Alle Fehler in Bezug auf Datenbankoperationen erben von dieser Klasse. -
UserFacingError
Jeder Fehler, der durch die Interaktion eines Benutzers mit der Anwendung entsteht, wird von dieser Klasse geerbt.
So würde unsere error
aussehen:
'use strict'; // Here is the base error classes to extend from class ApplicationError extends Error { get name() { return this.constructor.name; } } class DatabaseError extends ApplicationError { } class UserFacingError extends ApplicationError { } module.exports = { ApplicationError, DatabaseError, UserFacingError }
Dieser Ansatz ermöglicht es uns, die von unserer Anwendung ausgelösten Fehler zu unterscheiden. Wenn wir also jetzt einen fehlerhaften Anforderungsfehler (ungültige Benutzereingabe) oder einen nicht gefundenen Fehler (Ressource nicht gefunden) behandeln möchten, können wir von der Basisklasse UserFacingError
(wie im folgenden Code).
const { UserFacingError } = require('./baseErrors') class BadRequestError extends UserFacingError { constructor(message, options = {}) { super(message); // You can attach relevant information to the error instance // (eg. the username) for (const [key, value] of Object.entries(options)) { this[key] = value; } } get statusCode() { return 400; } } class NotFoundError extends UserFacingError { constructor(message, options = {}) { super(message); // You can attach relevant information to the error instance // (eg. the username) for (const [key, value] of Object.entries(options)) { this[key] = value; } } get statusCode() { return 404 } } module.exports = { BadRequestError, NotFoundError }
Einer der Vorteile des error
besteht darin, dass jeder Entwickler, der diese Codebasis liest, verstehen kann, was zu diesem Zeitpunkt vor sich geht (wenn er den Code liest), wenn wir einen dieser Fehler ausgeben, z. B. einen NotFoundError
).
Sie könnten auch während der Instanziierung dieses Fehlers mehrere Eigenschaften übergeben, die für jede Fehlerklasse spezifisch sind.
Ein weiterer wichtiger Vorteil besteht darin, dass Sie Eigenschaften haben können, die immer Teil einer Fehlerklasse sind. Wenn Sie beispielsweise einen UserFacing-Fehler erhalten, wissen Sie, dass ein statusCode immer Teil dieser Fehlerklasse ist. Jetzt können Sie ihn einfach direkt in der Code später.
Tipps zur Verwendung von Fehlerklassen
- Erstellen Sie für jede Fehlerklasse ein eigenes Modul (möglicherweise ein privates), damit Sie es einfach in Ihre Anwendung importieren und überall verwenden können.
- Geben Sie nur Fehler aus, die Sie interessieren (Fehler, die Instanzen Ihrer Fehlerklassen sind). Auf diese Weise wissen Sie, dass Ihre Fehlerklassen Ihre einzige Quelle der Wahrheit sind und alle Informationen enthalten, die zum Debuggen Ihrer Anwendung erforderlich sind.
- Ein abstraktes Fehlermodul zu haben ist sehr nützlich, da wir jetzt wissen, dass alle notwendigen Informationen zu Fehlern, die unsere Anwendungen auslösen können, an einem Ort sind.
- Behandeln Sie Fehler in Ebenen. Wenn Sie Fehler überall behandeln, haben Sie einen inkonsistenten Ansatz zur Fehlerbehandlung, der schwer zu verfolgen ist. Mit Ebenen meine ich Datenbank-, Express-/Fastify-/HTTP-Ebenen und so weiter.
Mal sehen, wie Fehlerklassen im Code aussehen. Hier ist ein Beispiel in Express:
const { DatabaseError } = require('./error') const { NotFoundError } = require('./userFacingErrors') const { UserFacingError } = require('./error') // Express app.get('/:id', async function (req, res, next) { let data try { data = await database.getData(req.params.userId) } catch (err) { return next(err); } if (!data.length) { return next(new NotFoundError('Dataset not found')); } res.status(200).json(data) }) app.use(function (err, req, res, next) { if (err instanceof UserFacingError) { res.sendStatus(err.statusCode); // or res.status(err.statusCode).send(err.errorCode) } else { res.sendStatus(500) } // do your logic logger.error(err, 'Parameters: ', req.params, 'User data: ', req.user) });
Aus dem oben Gesagten machen wir uns zunutze, dass Express einen globalen Fehlerbehandler bereitstellt, mit dem Sie alle Ihre Fehler an einem Ort behandeln können. Sie können den Aufruf von next()
an den Stellen sehen, an denen wir Fehler behandeln. Dieser Aufruf würde die Fehler an den Handler weitergeben, der im Abschnitt app.use
definiert ist. Da Express async/await nicht unterstützt, verwenden wir try/catch
-Blöcke.
Um unsere Fehler zu behandeln, müssen wir also anhand des obigen Codes nur prüfen, ob der ausgelöste Fehler eine UserFacingError
Instanz ist, und wir wissen automatisch, dass es einen statusCode im Fehlerobjekt geben würde, und wir senden diesen an den Benutzer (vielleicht möchten Sie auch einen bestimmten Fehlercode zu haben, den Sie an den Client weitergeben können) und das war es auch schon.
Sie würden auch bemerken, dass in diesem Muster ( error
) jeder andere Fehler, den Sie nicht explizit ausgelöst haben, ein 500
-Fehler ist, da es sich um etwas Unerwartetes handelt, das bedeutet, dass Sie diesen Fehler nicht explizit in Ihrer Anwendung ausgelöst haben. Auf diese Weise sind wir in der Lage, die Arten von Fehlern in unseren Anwendungen zu unterscheiden.
Fazit
Die richtige Fehlerbehandlung in Ihrer Anwendung kann Ihnen helfen, nachts besser zu schlafen und Debug-Zeit zu sparen. Hier sind einige wichtige Punkte, die Sie aus diesem Artikel mitnehmen können:
- Verwenden Sie speziell für Ihre Anwendung eingerichtete Fehlerklassen;
- Implementieren Sie abstrakte Fehlerbehandlungsroutinen;
- Verwenden Sie immer async/await;
- Machen Sie Fehler ausdrucksstark;
- Versprechen des Benutzers, falls erforderlich;
- Geben Sie die richtigen Fehlerstatus und -codes zurück;
- Nutzen Sie Promise Hooks.
Nützliche Frontend- und UX-Bits, die einmal pro Woche geliefert werden.
Mit Tools, die Ihnen helfen, Ihre Arbeit besser zu erledigen. Melden Sie sich an und erhalten Sie Vitalys Smart Interface Design-Checklisten im PDF -Format per E-Mail.
Auf Front-End & UX. Vertrauen von 190.000 Menschen.