Node.js und meine Base-Error-Klasse

Im Laufe der Arbeit mit Node.js bin ich immer wieder an den Punkt gestoßen, dass ich enorme Probleme bei der Fehlerbehandlung hatte. Insbesondere der Aspekt, dass ein Fehler nicht zwangsläufig ein Error-Objekt ist, sondern auch eine Zeichenkette sein kann, führte zu Schwierigkeiten. Ich zeige dir, wie ich dieser Herausforderung begegne.

3 Minuten

Mein erster Schritt, um der Lage Herr zu werden, ist die Einführung eines BaseError. In meinem Code werfe ich stets Fehler, die von dieser Klasse abgeleitet werden. Mithilfe dieses Ansatzes kann ich mich bei der Fehlerbehandlung darauf beschränken, Fehler Dritter in diese Klasse zu übertragen und ansonsten die korrekte Behandlung von BaseError zu implementieren.

Der Base-Error

Meinen Base-Error leite ich von der JavaScript-Klasse Error ab. Es gibt keinen Grund von diesem Sprachkonstrukt abzuweichen. Zusätzlich bette ich noch zwei Methoden ein, die mir während der täglichen Arbeit helfen:

  • toJSON() als Hilfsmethode zur Übersetzung eines Fehlers in ein JSON-Objekt, z. B. für das Logging (sofern im JSON-Format erfolgt).
  • toString() als Hilfsmethode zur Serialisierung als Text. Diese verlässt sich intern auf die JSON-Repräsentation. Hierbei ist Vorsicht geboten: Zyklische Referenzen führen bei diesen Aufrufen zu einer Fehlermeldung. Das fange ich in meiner toString()-Funktion ab.

Eine wichtige Zeile möchte ich hervorheben: Error.captureStackTrace(this, this.constructor). Damit wird die Error-Property stack mit wichtigen Details zum StackTrace des Fehlers befüllt.

import { toString } from '@/utils/StringUtils'  

export interface BaseErrorOptions {  
  detail?: string  
  cause?: unknown  
  context?: unknown  
  statusCode?: number  
}  

class BaseError extends Error {  
  public readonly detail: string  
  public readonly cause?: unknown  
  public readonly context?: unknown  
  public readonly statusCode: number  

  constructor(options: BaseErrorOptions) {  
    const { detail = 'Unknown error detail', cause, context } = options  
    super(detail)  
    Error.captureStackTrace(this, this.constructor)  
    this.detail = detail  
    this.cause = cause  
    this.context = context  
    this.statusCode = options.statusCode || 500  
  }  

  public toJSON() {  
    return {  
      detail: this.detail,  
      cause: this.cause,  
      context: this.context,  
      statusCode: this.statusCode,  
      stack: this.stack,  
    }  
  }  

  public toString() {  
    return toString(this.toJSON())  
  }  
}  

export default BaseError

toString() Hilfsmethode

Um den zyklischen Referenzen entgegenzuwirken, nutze ich einen plumpen und effektiven Ansatz. Ich bediene mich des NPM-Pakets flatted. Das ist ein schlanker JSON-Parser, der mit zyklischem JSON umgehen kann. Das Ergebnis ist nicht lesbar, doch besser als die Alternative: entweder nichts ausgegeben zu bekommen oder einen Fehler zu erhalten.

import { stringify } from 'flatted'

export const toString = (value: unknown): string => {  
  if (value === undefined) {  
    return 'undefined'  
  }  

  if (value === null) {  
    return 'null'  
  }  

  if (typeof value === 'string') {  
    return value  
  }  

  try {  
    return JSON.stringify(value)  
  } catch {  
    return stringify(value)  
  }  
}

Entweder JSON.stringify() schafft es oder ich nutze die Methode von flatted

toJSON() für Error-Objekte

Bekanntlich liefert JSON.stringify(error) nur ein leeres Objekt zurück. Der Hintergrund ist, dass das Error-Objekt keine enumerierbaren Properties hat. Um dies an zentraler Stelle zu umgehen, empfehle ich, folgenden kleinen Codeschnipsel einzubauen:

if (!('toJSON' in Error.prototype)) {  
  Object.defineProperty(Error.prototype, 'toJSON', {
    value: function () {
      const alt: Record<string, unknown> = {
      }

      Object.getOwnPropertyNames(this).forEach((key: string) => {
        alt[key] = this[key]
      }, this)

      return alt
    },
    configurable: true,  
    writable: true,  
  })  
}

Eine umfangreiche Diskussion zu dem Thema findet ihr auf StackOverflow

Wie gehst du mit Fehlern in Node.js um?

Lass uns schnacken
call to action background image

Abonniere meinen Newsletter

Erhalte einmal im Monat Nachrichten aus den Bereichen Softwareentwicklung und Kommunikation gespikt mit Buch- und Linkempfehlungen.