Menu

Try catch in TypeScript: tipi di errore ed errori personalizzati

La gestione degli errori in TypeScript: perché la variabile del catch è unknown, come restringerla con instanceof Error, lanciare errori, scrivere classi di errore personalizzate con name e cause, e il pattern del tipo Result per gli errori che ti aspetti.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

TypeScript usa try, catch, finally e throw di JavaScript. La parte specifica di TypeScript è la variabile del catch: con strict attivo ha tipo unknown, quindi controlli cosa è stato lanciato prima di usarlo.

Il lato runtime (come si srotola lo stack, quali tipi di errore predefiniti esistono) è trattato in try/catch in JavaScript.

Perché la variabile del catch è unknown

JavaScript può lanciare qualsiasi cosa: un Error, una stringa, un numero, undefined, un oggetto di una libreria. TypeScript non può sapere quale, quindi con strict (il flag useUnknownInCatchVariables) la variabile è unknown e devi restringerla prima di usarla:

index.ts(5,34): error TS18046: 'e' is of type 'unknown'.

Senza strict, e è any e lo stesso codice compila, poi va storto a runtime quando viene lanciata una stringa: e.message è undefined, e qualsiasi cosa più profonda, come e.message.length, lancia un TypeError. Non puoi nemmeno cavartela con un'annotazione: catch (e: Error) è l'errore TS1196 (Catch clause variable type annotation must be 'any' or 'unknown' if specified).

Restringere l'errore

instanceof Error copre tutto ciò che è costruito su Error, compresi TypeError, SyntaxError, RangeError e le tue sottoclassi. Per tutto il resto, ripiega sulla conversione del valore in stringa. Un piccolo helper tiene brevi i punti di chiamata:

Un catch che gestisce solo alcuni errori deve rilanciare gli altri: if (!(e instanceof ValidationError)) throw e;. Ingoiare gli errori sconosciuti nasconde bug reali.

Lanciare errori

throw accetta qualsiasi espressione, e TypeScript non lo limita. Lancia comunque oggetti Error: portano con sé uno stack trace, e ogni controllo instanceof Error del codebase dipende da questo.

Una funzione che lancia sempre un errore ha tipo di ritorno never, che TypeScript usa per restringere dopo la chiamata:

Da ES2022, Error accetta un secondo argomento con una cause, che collega un errore di basso livello a quello che lanci: throw new Error("could not load settings", { cause: e }). Chi chiama può leggere err.cause (tipizzato unknown).

Classi di errore personalizzate

Una sottoclasse di Error permette a chi chiama di distinguere i fallimenti con instanceof e di portare dati extra. Imposta name, perché quello ereditato è "Error" e compare nei log e in String(err):

Controlla per prima la classe più specifica, perché un NotFoundError è anche un HttpError. Il modificatore override qui è facoltativo, a meno che noImplicitOverride sia attivo.

Le guide più vecchie aggiungono Object.setPrototypeOf(this, new.target.prototype) a ogni costruttore di errore. Serviva quando le classi venivano compilate in funzioni ES5, dove instanceof si rompeva per le sottoclassi di Error. TypeScript 7 non supporta più target: "es5" (segnala TS5108: Option 'target=ES5' has been removed), e con ES2015 o successivi il nativo class ... extends Error funziona, come sopra.

Il pattern Result

TypeScript non tiene traccia degli errori che una funzione può lanciare, quindi nulla ricorda a chi chiama di gestirli. Per i fallimenti previsti (input non valido, un record mancante), restituire un valore che dice "successo o fallimento" porta l'errore dentro il sistema di tipi:

Leggere r.value prima di controllare r.ok è un errore di compilazione, ed è proprio questo lo scopo. Usa le eccezioni per l'imprevisto (bug, una connessione caduta) e i valori Result per gli esiti su cui chi chiama deve decidere.

Errori nel codice asincrono

Una promise rifiutata diventa un errore lanciato nel punto dell'await, quindi try/catch attorno ad await funziona allo stesso modo, e la variabile è di nuovo unknown. L'unica differenza è .catch() su una promise: il parametro della sua callback è tipizzato any, non unknown, quindi annotalo tu (.catch((e: unknown) => ...)). Vedi async/await per degli esempi.

Errori comuni

  • Leggere e.message senza restringere. Con strict non compila; senza strict si rompe quando viene lanciato qualcosa che non è un Error.
  • Catturare tutto e andare avanti. Gestisci gli errori che ti aspetti e rilancia gli altri.
  • Dimenticare name negli errori personalizzati. I log diranno allora Error per tutti.
  • Lanciare stringhe. throw "failed" non ha stack trace e non supera i controlli instanceof Error.

Domande frequenti

Di che tipo è l'errore in un blocco catch di TypeScript?

unknown quando strict è attivo (il flag useUnknownInCatchVariables), altrimenti any. JavaScript può lanciare qualsiasi valore, non solo oggetti Error, quindi TypeScript ti obbliga a controllare. Restringilo con if (e instanceof Error) prima di leggere e.message.

Posso tipizzare la variabile del catch come Error in TypeScript?

No. catch (e: Error) è l'errore TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified. Sono ammessi solo unknown e any, perché nulla garantisce cosa sia stato lanciato. Restringi invece dentro il blocco.

Come si lancia un errore in TypeScript?

Come in JavaScript: throw new Error("message"), o un'istanza di una sottoclasse predefinita o personalizzata come new RangeError(...). TypeScript ti lascia lanciare qualsiasi valore, ma lanciare oggetti Error conserva lo stack trace e fa funzionare i controlli instanceof Error.

Come si crea una classe di errore personalizzata in TypeScript?

Estendi Error, chiama super(message, options) e imposta name: class NotFoundError extends Error { name = "NotFoundError"; }. I campi extra vanno nel costruttore. Con qualsiasi target supportato (ES2015 e successivi), instanceof NotFoundError funziona senza correzioni al prototipo.

TypeScript ha eccezioni controllate o una clausola throws?

No. Il tipo di una funzione non dice nulla su cosa può lanciare, e il compilatore non verifica mai che gli errori vengano gestiti. Per gli errori che chi chiama deve gestire, restituiscili come valori con un tipo Result (una discriminated union di successo e fallimento), così il sistema di tipi li controlla davvero.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA