Treci la conținutul principal

Gestionarea erorilor în TypeScript

Gestionarea corectă a erorilor este esențială pentru a construi aplicații TypeScript fiabile.

Această pagină pornește de la blocul de bază try/catch și ajunge la modele mai avansate: clase de eroare personalizate, gardieni de tip, erori asincrone și bune practici.


Gestionarea de bază a erorilor

Codul care poate eșua se pune într-un bloc try. Dacă acolo este aruncat ceva, execuția sare la blocul catch, unde primești valoarea aruncată.

Notă: în TypeScript 4.0 și mai nou (cu useUnknownInCatchVariables, pe care strict îl activează) variabila din catch are tipul unknown. Restrânge-i întotdeauna tipul înainte de a citi proprietăți precum message.

Blocuri try/catch

Fundamentul gestionării erorilor în TypeScript: divide aruncă o eroare la împărțirea la zero, iar blocul catch o raportează.

Prompt
Arată modelul de bază try/catch, cu o funcție care aruncă o eroare. Scrie divide(a: number, b: number): number care aruncă new Error("Împărțire la zero") atunci când b este 0. Apelează divide(10, 0) într-un bloc try care ar afișa rezultatul, iar în catch afișează A apărut o eroare: urmat de mesajul erorii, folosind console.error.
Ieșire: program TypeScript și rezultatul capturat

Clase de eroare personalizate

Poți extinde clasa încorporată Error pentru a crea erori care poartă informații suplimentare pentru domeniul tău, cum ar fi câmpul care a picat validarea sau un cod de eroare din baza de date.

Crearea claselor de eroare personalizate

ValidationError și DatabaseError extind Error, își stabilesc propriul name și restaurează lanțul de prototipuri, astfel încât instanceof să funcționeze și după compilarea către ținte mai vechi.

Prompt
Arată cum se creează clase de eroare specifice domeniului, extinzând Error. Definește ValidationError (mesaj plus un câmp public opțional field) și DatabaseError (mesaj plus un câmp public numeric code); ambele stabilesc this.name și restaurează lanțul de prototipuri cu Object.setPrototypeOf. Adaugă o funcție validateUser(user: any) care aruncă o ValidationError pentru un nume lipsă (câmpul name) sau pentru un email fără @ (câmpul email). Doar declarații — nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat

Gardieni de tip pentru erori

Deoarece o valoare capturată poate fi orice, scrie gardieni de tip — funcții care returnează error is SomeType — pentru a verifica ce ai capturat de fapt, înainte de a folosi valoarea.

Pentru cazuri mai complexe poți folosi și o funcție de aserțiune (asserts error is Error), arătată în fragmentul de mai jos: după apel, TypeScript tratează valoarea ca fiind Error.

Predicate de tip pentru gestionarea erorilor

Doi gardieni de tip permit blocului catch să trateze o ValidationError, orice obiect cu un message de tip șir de caractere sau o valoare complet necunoscută.

Prompt
Arată gardieni de tip care restrâng în siguranță o eroare capturată de tip unknown. Refolosește o clasă ValidationError (mesaj și câmpul opțional field) și o funcție validateUser care o aruncă atunci când name lipsește. Scrie isErrorWithMessage(error: unknown): error is { message: string } (obiect, diferit de null, care are un message de tip șir de caractere) și isValidationError(error: unknown): error is ValidationError folosind instanceof. Apelează validateUser({}) într-un bloc try; în catch (error: unknown) afișează Eroare de validare în <field>: <message> pentru erorile de validare, A apărut o eroare: <message> pentru alte erori care au un mesaj, sau A apărut o eroare necunoscută.
Ieșire: program TypeScript și rezultatul capturat

Funcție de aserțiune pentru erori

assertIsError aruncă o eroare dacă valoarea nu este un Error; altfel, TypeScript restrânge valoarea capturată la Error pentru restul blocului.

script.ts
function assertIsError(error: unknown): asserts error is Error {
  if (!(error instanceof Error)) {
    throw new Error('Caught value is not an Error instance');
  }
}

try {
  // ...
} catch (error) {
  assertIsError(error);
  console.error((error as Error).message); // TypeScript now knows error is Error
}

Gestionarea erorilor asincrone

Cu async/await, erorile provenite din promisiuni așteptate sunt capturate de un try/catch obișnuit. Cu promisiuni simple, înlănțuiește în schimb un handler .catch().

Nu lăsa niciodată o respingere de promisiune netratată. Fie înlănțuiești .catch(), fie folosești void pentru a arăta că ignorarea rezultatului este intenționată, dar erorile sunt totuși capturate.

Tratarea erorilor async/await

Învelirea unui await în try/catch îți permite să înregistrezi eroarea și să o re-arunci; un lanț de promisiuni se poate reface cu o valoare de rezervă în .catch(). Apelul de rețea este înlocuit cu un simulacru local, astfel încât exemplul să ruleze oriunde.

Prompt
Arată tratarea erorilor în cod asincron, atât cu async/await plus try/catch, cât și cu .catch() pe promisiune. Definește interfețele User și Post și o funcție locală mockFetch(url) care returnează un răspuns fals rezolvat (un utilizator John Doe pentru /api/users/1, o postare cu titlul Salut, lume pentru URL-ul /posts), astfel încât să nu se folosească rețeaua. Scrie fetchUser(userId): Promise<User> care așteaptă răspunsul, aruncă Eroare HTTP! status: <status> atunci când răspunsul nu este ok, afișează Preluarea utilizatorului a eșuat: în blocul său catch și re-aruncă eroarea; și fetchUserPosts(userId): Promise<Post[]> folosind .then() cu aceeași verificare și un .catch() care afișează Preluarea postărilor a eșuat: și returnează []. Apelează ambele funcții și afișează numele utilizatorului și tabloul de postări.
Ieșire: program TypeScript și rezultatul capturat

Tratarea respingerilor de promisiuni

O promisiune fără .catch() este o problemă care așteaptă să se întâmple; cele două forme corecte tratează respingerea sau ignoră explicit rezultatul cu void.

script.ts
// Bad: Unhandled promise rejection
fetchData().then(data => console.log(data));

// Good: Handle both success and error cases
fetchData()
  .then(data => console.log('Success:', data))
  .catch(error => console.error('Error:', error));

// Or use void for intentionally ignored errors
void fetchData().catch(console.error);

Limite de eroare în React

Într-o aplicație React, o limită de eroare este o componentă de tip clasă care capturează erorile JavaScript aruncate oriunde în arborele componentelor sale copil și afișează o interfață de rezervă în loc să prăbușească întreaga pagină.

Componentă de limită de eroare React

O clasă ErrorBoundary cu props și stare tipizate, care reține eroarea în getDerivedStateFromError, o înregistrează în jurnal în componentDidCatch și randează o interfață de rezervă.

script.ts
import React, { Component, ErrorInfo, ReactNode } from 'react';

interface ErrorBoundaryProps {
  children: ReactNode;
  fallback?: ReactNode;
}

interface ErrorBoundaryState {
  hasError: boolean;
  error?: Error;
}

class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
  public state: ErrorBoundaryState = {
    hasError: false
  };

  public static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { hasError: true, error };
  }

  public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error('Uncaught error:', error, errorInfo);
    // Log to error reporting service
  }

  public render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div className="error-boundary">
          <h2>Something went wrong</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false })}>
            Try again
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// Usage
function App() {
  return (
    <ErrorBoundary fallback={<div>Oops! Something broke.</div>}>
      <MyComponent />
    </ErrorBoundary>
  );
}

Bune practici

Tratează întotdeauna erorile. Nu lăsa niciodată un bloc catch gol — cel puțin înregistrează eroarea în jurnal.

Folosește tipuri de eroare specifice. Creează o clasă de eroare personalizată pentru fiecare fel de eșec, astfel încât apelanții să le poată deosebi.

Tratează erorile la nivelul potrivit. Capturează o eroare acolo unde ai suficient context ca să te poți reface sau ca să afișezi un mesaj util utilizatorului — adesea nu în aceeași funcție care o aruncă.

Nu lăsa niciodată catch gol

Un catch tăcut ascunde eșecurile; înregistrarea erorii în jurnal este minimul necesar.

script.ts
// Bad: Silent failure
try { /* ... */ } catch { /* empty */ }

// Good: At least log the error
try { /* ... */ } catch (error) {
  console.error('Operation failed:', error);
}

Clase de eroare specifice

NetworkError poartă un status HTTP, iar ValidationError numele câmpului care a picat validarea.

script.ts
class NetworkError extends Error {
  constructor(public status: number, message: string) {
    super(message);
    this.name = 'NetworkError';
  }
}

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

Tratarea erorilor la nivelul potrivit

Stratul de date aruncă o NetworkError; stratul de interfață o capturează și decide ce mesaj să afișeze.

script.ts
// In a data access layer
async function getUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  if (!response.ok) {
    throw new NetworkError(response.status, 'Failed to fetch user');
  }
  return response.json();
}

// In a UI component
async function loadUser() {
  try {
    const user = await getUser('123');
    setUser(user);
  } catch (error) {
    if (error instanceof NetworkError) {
      if (error.status === 404) {
        showError('User not found');
      } else {
        showError('Network error. Please try again later.');
      }
    } else {
      showError('An unexpected error occurred');
    }
  }
}

Capcane frecvente

Netratarea respingerilor de promisiuni. Atașează întotdeauna un handler promisiunilor, altfel primești avertismente de respingere netratată și pierzi erori.

Capturarea fără restrângerea tipului. În TypeScript 4.0+ valoarea capturată este unknown; citirea lui error.message fără o verificare este o eroare de compilare.

Înghițirea erorilor. Nu captura o eroare doar ca să o ignori. Înregistreaz-o în jurnal și, acolo unde are sens, anunță utilizatorul.

Rezumat — gestionarea eficientă a erorilor în TypeScript înseamnă:

  • Folosirea try/catch pentru cod sincron
  • Tratarea respingerilor de promisiuni cu .catch() sau cu try/catch împreună cu async/await
  • Crearea de clase de eroare personalizate pentru erorile specifice domeniului
  • Folosirea gardienilor de tip pentru a lucra în siguranță cu obiectele de eroare
  • Tratarea erorilor la nivelul potrivit al aplicației
  • Oferirea de mesaje de eroare utile utilizatorilor

Respingere de promisiune netratată

Apelarea unei funcții asincrone și abandonarea promisiunii, în comparație cu atașarea unui .catch().

script.ts
// Bad: Unhandled promise rejection
fetchData();

// Good: Handle the rejection
fetchData().catch(console.error);

Capturare fără restrângerea tipului

Citirea lui message dintr-o eroare de tip unknown eșuează; o verificare instanceof Error o face sigură.

script.ts
// Bad: Error is of type 'unknown'
try { /* ... */ } catch (error) {
  console.log(error.message); // Error: Property 'message' does not exist on type 'unknown'
}

// Good: Narrow the type
try { /* ... */ } catch (error) {
  if (error instanceof Error) {
    console.log(error.message); // OK
  }
}

Înghițirea erorilor

Prima funcție saveData ascunde eșecurile; a doua le înregistrează în jurnal și anunță utilizatorul.

script.ts
// Bad: Error is silently ignored
function saveData(data: Data) {
  try {
    database.save(data);
  } catch {
    // Ignore
  }
}

// Better: Log the error and/or notify the user
function saveData(data: Data) {
  try {
    database.save(data);
  } catch (error) {
    console.error('Failed to save data:', error);
    showError('Failed to save data. Please try again.');
  }
}