try/catch è una rete di sicurezza, non una cintura
Quando una riga di JavaScript lancia un errore, l'esecuzione si ferma di colpo e l'errore risale lo stack delle chiamate. Se nessuno lo cattura, il programma va in crash (in Node) o riempie la console di rosso (nei browser). try/catch è il modo per intercettarlo, per dire "so che questo potrebbe fallire, ecco cosa voglio fare invece."
La forma di base:
JSON.parse lancia un SyntaxError. L'esecuzione salta subito al blocco catch con l'errore assegnato a err. Il terzo console.log viene comunque eseguito: il crash è stato contenuto.
Se il blocco try va a buon fine senza lanciare errori, il blocco catch viene saltato del tutto. Esiste solo per il percorso di errore.
L'oggetto Error
Qualunque cosa venga lanciata finisce nel parametro indicato in catch (...). Di solito è un'istanza di Error con tre campi utili:
name ti dice la sottoclasse (TypeError, RangeError, SyntaxError e così via: ne parliamo nella prossima pagina). message è la descrizione leggibile. stack è la traccia completa, preziosissima durante il debug.
Un'insidia: JavaScript ti permette di fare throw di qualsiasi cosa, non solo di oggetti Error. Il codice vecchio a volte fa throw "qualcosa si è rotto". Quando scrivi i tuoi throw, lancia sempre un Error, così chi lo cattura ottiene uno stack trace:
finally viene eseguito in ogni caso
finally è un terzo blocco opzionale che viene eseguito sia che sia stato lanciato un errore sia che no, e sia che il catch l'abbia gestito sia che no. Serve per la pulizia: chiudere file, rilasciare lock, nascondere gli spinner di caricamento:
Lo spinner viene nascosto sia che il caricamento riesca sia che fallisca. Senza finally dovresti scrivere quella riga in entrambi i rami, e prima o poi te la dimenticheresti in uno dei due.
finally viene eseguito anche se il blocco try o catch contiene un return. La funzione restituisce il valore dopo l'esecuzione di finally. A volte sorprende; quasi sempre è esattamente quello che vuoi.
Non sempre serve catch
catch è opzionale. Un semplice try/finally è valido e utile quando vuoi una pulizia garantita ma non hai intenzione di gestire l'errore: vuoi che si propaghi:
Il try/finally interno rilascia il lock anche quando fn() lancia un errore, ma non lo inghiotte: il chiamante lo vede comunque. Inghiottire gli errori in silenzio ("è fallito e non l'ho detto a nessuno") è uno degli incubi più comuni durante il debug.
Rilanciare: gestisci alcuni errori, passa gli altri
Un blocco catch non deve per forza gestire tutto. Puoi ispezionare l'errore, gestire quello che ha senso e rilanciare il resto:
Il controllo con instanceof è lo schema da seguire: riconosci gli errori da cui sai riprenderti e lascia che tutto il resto continui a risalire lo stack. Inghiottire ogni errore con un blocco catch vuoto è un segnale di codice problematico: perdi ogni indizio quando qualcosa di inatteso va storto.
try/catch con async/await
Dentro una funzione async, le Promise rifiutate su cui fai await diventano errori lanciati, e try/catch le gestisce come gli errori sincroni:
Una sottigliezza: devi fare await della Promise dentro il blocco try. Se restituisci una Promise senza attenderla, il rifiuto avviene dopo che la funzione è già uscita, e catch non lo vede mai:
async function bad() {
try {
return fetch("/broken"); // no await - caller sees the rejection
} catch (err) {
// never runs
}
}
Regola pratica: nelle funzioni async, fai await di ciò che vuoi che try/catch copra.
try/catch annidati
Puoi annidare i blocchi try/catch quando il codice interno e quello esterno falliscono per motivi diversi che vuoi gestire in modo diverso:
Il catch interno gestisce il caso "i dati hanno la forma sbagliata" restituendo un valore predefinito sicuro. Quello esterno gestisce il caso "l'input non era nemmeno JSON" avvolgendo e rilanciando l'errore. Annidare va bene quando ogni livello ha una strategia di recupero distinta; se entrambi i blocchi farebbero la stessa cosa, appiattiscili.
Quando non usare try/catch
try/catch è uno strumento per i fallimenti previsti e recuperabili. Non è un modo per nascondere i bug.
- Non avvolgere l'intero corpo di una funzione "per sicurezza". Se non hai un vero piano per l'errore, lascialo risalire: un errore non catturato con il suo stack trace è più utile di uno silenzioso.
- Non usarlo per il controllo del flusso. I blocchi
tryhanno un costo reale e rendono il codice più confuso rispetto a un controllo conif.if (user)battetry { user.name } catch {}. - Non catturare, registrare e ignorare. Come minimo, rilancia o restituisci un valore sentinella che il chiamante possa riconoscere.
Il test mentale: "cosa fa chi usa questo codice quando fallisce?" Se non hai una risposta, non sei ancora pronto per fare catch dell'errore.
Riepilogo rapido
try { ... } catch (err) { ... }: intercetta gli errori lanciati.finally { ... }: viene eseguito sempre; usalo per la pulizia.throw new Error("..."): lancia sempre sottoclassi diError, così gli stack trace funzionano.throw err;dentrocatch: rilancia quando non puoi gestire l'errore.awaitdentrotry: necessario perchétry/catchveda i rifiuti asincroni.
Prossimo passo: i tipi di errore
TypeError, RangeError, SyntaxError: JavaScript ha una famiglia di classi di errore integrate, e sapere cosa significa ciascuna rende molto più preciso catturarle e segnalarle. È l'argomento della prossima pagina.
Domande frequenti
Come funziona try/catch in JavaScript?
Metti il codice rischioso dentro try { ... }. Se qualcosa al suo interno lancia un errore, l'esecuzione salta subito al blocco catch (err) { ... }, con il valore lanciato assegnato a err. Se non viene lanciato nulla, il blocco catch viene saltato. Un blocco opzionale finally { ... } viene eseguito in ogni caso: utile per le operazioni di pulizia.
Quando usare try/catch in JavaScript?
Usalo attorno alle operazioni che possono fallire davvero a runtime: JSON.parse su input non affidabili, risposte di fetch, I/O su file o rete. Non avvolgere ogni riga: se non hai un piano per recuperare, lascia che l'errore risalga. Un try/catch generico attorno a codice che funziona nasconde i bug invece di gestirli.
try/catch cattura gli errori asincroni?
Solo se fai await della Promise dentro il blocco try. Una semplice chiamata somePromise() non viene catturata: l'errore diventa un unhandled rejection. Con async/await, try/catch funziona come con il codice sincrono. Con le Promise senza await, usa invece .catch() sulla catena.
Come rilancio un errore in JavaScript?
Dentro catch, scrivi semplicemente throw err; (oppure lancia un nuovo errore che lo avvolge). È utile quando vuoi gestire alcuni errori e lasciar propagare gli altri: controlla prima il tipo o il messaggio dell'errore, gestisci quello che puoi e rilancia il resto, così i chiamanti più in alto lo vedono comunque.