Entwickler, der außergewöhnliche CRM- und Laravel-Lösungen liefert

Als erfahrener Entwickler spezialisiere ich mich auf Laravel- und Vue.js-Entwicklung, die Implementierung von Vtiger CRM sowie auf vielfältige WordPress-Projekte. Meine Arbeit zeichnet sich durch kreative, dynamische und benutzerzentrierte Weblösungen aus, die individuell an die Bedürfnisse meiner Kunden angepasst werden.

Das Thema der Fehlerbehandlung in Javascript kommt fast jedem in den Sinn. Ich sollte anmerken, dass zu diesem Thema schon eine ganze Menge Artikel geschrieben wurden. Deshalb werde ich nicht auf Details und Grundlagen eingehen. Ich werde meine eigene Variante der Fehlerbehandlung anbieten.

Funktionale Fehlerbehandlung in Javascript mit Either

Das Thema der Fehlerbehandlung in Javascript kommt fast jedem in den Sinn. Ich sollte anmerken, dass zu diesem Thema schon eine ganze Menge Artikel geschrieben wurden. Deshalb werde ich nicht auf Details und Grundlagen eingehen. Ich werde meine eigene Variante der Fehlerbehandlung anbieten.

Zu Beginn möchte ich Sie daran erinnern, dass ein Fehler in Javascript im Wesentlichen das „Auslösen einer Ausnahme“ ist. Später muss die Ausnahme von der Anwendung behandelt werden. Standardmäßig wird eine Ausnahme durch das Error-Objekt ausgelöst.

Der traditionelle try/catch-Ansatz hat seine Nachteile: fehlende strenge Typisierung im catch-Block, Vermischung von technischen Fehlern und Fehlern der Geschäftslogik und häufige Fälle, in denen Entwickler vergessen, Fehler an kritischen Stellen zu behandeln. Dieses Problem kann durch die Verwendung des Either-Musters gelöst werden, das eine explizite Trennung zwischen erfolgreichen und fehlerhaften Ergebnissen ermöglicht. In diesem Artikel wird beschrieben, wie die Fehlerbehandlung in TypeScript durch das Erstellen benutzerdefinierter Ausnahmen implementiert wird, und es wird gezeigt, wie die Monet-Bibliothek verwendet wird, um einen funktionalen Ansatz für die Fehlerbehandlung zu wählen.

Die übliche Art, Fehler zu behandeln, ist die Verwendung von try/catch-Blöcken. Dem catch-Block fehlt jedoch die Fehlertypisierung (der Standardtyp ist any), was zu Schwierigkeiten bei der weiteren Analyse der Ausnahme führen kann. Außerdem ist es schwierig, technische Fehler (z. B. Datenbankverbindungsfehler) von Fehlern in der Geschäftslogik zu trennen.

Um dieses Problem zu lösen, werden oft benutzerdefinierte Fehler geschrieben. Hier ist ein Beispiel für die Erstellung eines benutzerdefinierten Fehlers:

class OrderError extends Error {
  constructor(code: string, message: string) {
    super(message);
    this.name = "OrderError";
    this.code = code; // insufficient_funds, invalid_payment, incorrect_id etc
    const codeMessageMap = {
        'insufficient_funds': 'You do not have enough funds to complete this order',
        'invalid_payment': 'There was an error during last payment',
        'incorrect_id': 'Wrong ID was provided',
    }
    this.userMessage = codeMessageMap[code] || "An error occurred with your order"
  }
}

function processOrder(orderId: number): void {
  if (orderId <= 0) {
    throw new OrderError("incorrect_id", "Wrong ID");
  }
  
}

Mit diesem Ansatz können Sie den Fehlertyp expliziter definieren, aber der catch-Block selbst arbeitet weiterhin mit dem Typ any, was nicht immer praktisch ist.

So wird die Fehlerbehandlung in diesem Fall aussehen:

try{
    processOrder(2)
}catch(err){
    if(err instanceof OrderError){
        if(err.code === 'incorrect_id'){
            alert(err.userMessage)
            console.log(err.message);
        }
        if(err.code === 'invalid_payment'){
            // show message to user
        }
    } else{
        alert('Something went wrong! Please contact support')
        console.log(err.message);
    }
    
}

Javascript-Fehler unterstützen auch die Verschachtelung. Im folgenden Beispiel fangen wir einen Fehler ab, verpacken ihn in unseren eigenen, wobei der Kontext erhalten bleibt und eine klarere Beschreibung hinzugefügt wird.

function displayMoney(cents){
    try{
        document.querySelector("#money").innerText = formatMoney(cents)    
    }catch(err){
        if(err.message.includes('innerText')){
            throw new Error('Div with the id of money is missing from the page', {
                cause: err
            })
        }
        throw err;
    }
}

Es ist oft schwierig, technische Fehler von Fehlern in der Geschäftslogik zu unterscheiden. Betrachten wir das folgende Beispiel, bei dem nach der Verarbeitung einer API-Anforderung ein Fehler auftritt, so können wir auf der darüber liegenden Ebene nicht genau erkennen, ob das Problem technischer oder geschäftslogischer Natur ist.

async function fetchWithRetry(url, attempts = 3){
    while(attempts > 0){
        try{
            const res = await fetch(url)
            if(res.ok){
                return res
            }else{
                throw new Error("Network response was not ok")
            }
        }catch(err){
            attempts--;
            if(attempts === 0){
                throw err
            }
        }
    }
}

try{
    await fetchWithRetry("https://httpbin.org/status/200,500")
}catch(err){
    console.log("caught retry example", err)
}

console.log("end!")

2. Entweder-Prinzip und Vorteile der funktionalen Fehlerbehandlung

Das Entweder-Muster ist in der funktionalen Programmierung weit verbreitet. Es besteht im Wesentlichen darin, dass eine Funktion keine Ausnahme, sondern ein Ergebnis zurückgibt, das entweder erfolgreich (rechts) oder fehlerhaft (links) sein kann. Dieser Ansatz ähnelt der Fehlerbehandlung in Go, bei der Fehler zusammen mit dem Ergebnis zurückgegeben werden, anstatt weggeworfen zu werden.

Vorteile von Either:

  • Klare Trennung zwischen einem erfolgreichen Ergebnis und einem Fehler.
  • Möglichkeit, mehrere Funktionen zusammenzustellen, ohne dass mehrere try/catch-Blöcke erforderlich sind.
  • Transparenz bei der Fehlerbehandlung - das Ergebnis spiegelt explizit wider, ob die Operation erfolgreich ist oder nicht.

3. Ein Beispiel für die Implementierung der Fehlerbehandlung mit Either

In diesem Beispiel implementieren wir die Funktion zum Aktualisieren eines E-Mail-Clients in CRM. Wir verwenden die Monet-Bibliothek, um mit Either zu arbeiten. Wenn die Aktualisierung feststellt, dass der Client nicht existiert oder eine ungültige E-Mail übergeben wird, gibt die Funktion Either.left mit dem entsprechenden Fehler zurück. Andernfalls - Either.right mit den aktualisierten Kundendaten.

Code-Beispiel:

import { Either } from "monet";

interface Client {
  id: number;
  name: string;
  email: string;
}

class NotFoundError extends Error {
  constructor(code: int, message: string) {
    super(message);
    this.name = "NotFoundError";
    this.code = code
  }
}

class ValidationError extends Error {
  constructor(field: string, message: string) {
    super(message);
    this.name = "BusinessError";
    this.field = field;
  }
}

const fakeDatabase: Client[] = [
  { id: 1, name: "Иван Иванов", email: "ivan@example.com" },
  { id: 2, name: "Мария Петрова", email: "maria@example.com" }
];

function updateClientEmail(clientId: number, newEmail: string): Either<Error, Client> {
  const client: Client | undefined = fakeDatabase.find(client => client.id === clientId);
  
  if (!client) {
    return Either.Left(new NotFoundError(404, `Клиент с id=${clientId} не найден`));
  }
  
  if (!newEmail.includes("@")) {
    return Either.Left(new BusinessError("email", "Wrong email provided"));
  }
  
  client.email = newEmail;

  return Either.Right(client);
}

// How to use it
const result = updateClientEmail(1, "newemail@example.com");

result.cata(
  (error) => console.error("Ошибка при обновлении клиента:", error.message),
  (client) => console.log("Клиент успешно обновлен:", client)
);

In diesem Beispiel:

  • Die Funktion updateClientEmail gibt Either zurück.
  • Wenn ein Fehler auftritt (z. B. Client nicht gefunden oder E-Mail ist falsch), wird Either.Left mit dem Fehlerobjekt zurückgegeben.
  • Wenn alles erfolgreich war, wird Either.Right mit dem aktualisierten Client-Objekt zurückgegeben.
  • Mit der cata-Methode können Sie Either „aufklappen“ und eine der Funktionen ausführen, je nachdem, ob das Ergebnis erfolgreich oder fehlerhaft ist.

4. Erläuterung und Möglichkeiten für weitere Verbesserungen

Wie Either funktioniert:

  • Die linke Option (Left) steht für einen Fehler und die rechte Option (Right) für ein erfolgreiches Ergebnis.
  • Die Verwendung von flatMap-, map- und cata-Methoden macht es einfach, eine Folge von Operationen zusammenzustellen, ohne auf verschachtelte try/catch zurückgreifen zu müssen.

Weitere Bereiche für Verbesserungen:

  • Es ist möglich, die Funktionalität durch die Implementierung von Methoden zur Unterstützung der Komposition (z. B. flatMap) zu erweitern, um mehrere Funktionen zu kombinieren, die entweder zurückgeben.
  • Wenn die fp-ts Bibliothek verwendet wird, ist es möglich, die Fehlerbehandlung mit bereits vorgefertigten monadischen Konstrukten zu erweitern.
  • Das Hinzufügen von Logging und automatischer Fehlerprüfung zu CRM wird die Überwachung von Geschäftsprozessen verbessern.

Schlussfolgerung

Die Fehlerbehandlung ist ein wichtiger Aspekt bei der Entwicklung stabiler und skalierbarer CRM-Systeme. Traditionelles try/catch bietet nicht immer die notwendige Typisierung und Trennung von Fehlern, was zu Komplexität bei der Codewartung führen kann. Die Verwendung des Either-Patterns mit der Monet-Bibliothek zeigt, wie die Fehlerbehandlung deklarativ, leicht komponentisierbar und robust gestaltet werden kann. Das obige Beispiel zeigt, wie eine Funktion zur Aktualisierung von Client-Daten implementiert werden kann, indem erfolgreiche Ergebnisse und Fehlersituationen explizit voneinander getrennt werden. Dieser funktionale Ansatz vereinfacht die Skalierung und Integration von Systemkomponenten und macht den Code transparenter und überschaubarer.