Gestione Errori JavaScript

La gestione degli errori in JavaScript è essenziale per creare applicazioni robuste e affidabili. Gli errori sono inevitabili nello sviluppo software, ma una gestione appropriata può trasformare un crash dell’applicazione in un’esperienza utente fluida e informativa.
Tipi di Errori in JavaScript
JavaScript distingue diversi tipi di errori, ognuno con caratteristiche specifiche che aiutano a identificare e risolvere i problemi:
Errori di Sintassi: Si verificano quando il codice non rispetta la sintassi JavaScript. Questi vengono rilevati prima dell’esecuzione e impediscono al codice di funzionare.
Errori di Runtime: Avvengono durante l’esecuzione del programma, come tentare di accedere a proprietà di variabili undefined o chiamare funzioni inesistenti.
Errori Logici: Il codice viene eseguito senza errori tecnici, ma produce risultati incorretti. Questi sono i più difficili da individuare perché non generano eccezioni.
// Errore di sintassi
// let x = 5 + ; // SyntaxError
// Errore di runtime
let obj = null;
console.log(obj.nome); // TypeError: Cannot read property 'nome' of null
// Errore logico
function somma(a, b) {
return a * b; // Dovrebbe essere a + b
}
Il Blocco try…catch
Il costrutto try...catch è lo strumento principale per gestire gli errori in JavaScript. Permette di “catturare” errori che altrimenti fermerebbero l’esecuzione del programma:
try {
// Codice che potrebbe generare un errore
let risultato = operazioneRischiosa();
console.log("Operazione completata:", risultato);
} catch (errore) {
// Gestione dell'errore
console.log("Si è verificato un errore:", errore.message);
} finally {
// Codice che viene sempre eseguito
console.log("Pulizia e chiusura operazioni");
}
Il blocco finally è opzionale ma molto utile per operazioni di pulizia che devono essere eseguite indipendentemente dall’esito dell’operazione, come chiudere connessioni di database o liberare risorse.
Lanciare Errori Personalizzati
Oltre a catturare errori, puoi lanciare errori personalizzati usando throw. Questo è utile per validare input o segnalare condizioni impreviste:
function dividi(a, b) {
if (b === 0) {
throw new Error("Divisione per zero non consentita");
}
return a / b;
}
try {
let risultato = dividi(10, 0);
console.log(risultato);
} catch (errore) {
console.log("Errore nella divisione:", errore.message);
}
Puoi lanciare qualsiasi tipo di valore con throw, ma è buona pratica utilizzare oggetti Error per mantenere coerenza e fornire informazioni utili per il debugging.
Tipi di Errori Built-in
JavaScript fornisce diversi tipi di errori built-in per situazioni specifiche:
// TypeError - tipo di dato non corretto
let numero = 42;
numero.push(3); // TypeError: numero.push is not a function
// ReferenceError - variabile non definita
console.log(variabileInesistente); // ReferenceError
// RangeError - valore fuori intervallo
let array = new Array(-1); // RangeError: Invalid array length
// URIError - URI malformato
decodeURIComponent("%"); // URIError: URI malformed
Conoscere questi tipi ti aiuta a gestire errori specifici in modo più appropriato e a fornire messaggi di errore più informativi agli utenti.
Errori Asincroni
La gestione degli errori diventa più complessa con il codice asincrono. Promise e async/await hanno meccanismi specifici per la gestione degli errori:
Con Promise
fetch("/api/dati")
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then((dati) => console.log(dati))
.catch((errore) => {
console.log("Errore nel caricamento:", errore.message);
});
Con async/await
async function caricaDati() {
try {
const response = await fetch("/api/dati");
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const dati = await response.json();
return dati;
} catch (errore) {
console.log("Errore nel caricamento:", errore.message);
return null; // Valore di fallback
}
}
Strategie di Recupero
Una buona gestione degli errori non si limita a registrarli, ma implementa strategie di recupero quando possibile:
Valori di Fallback
function ottieniConfigutazione() {
try {
return JSON.parse(localStorage.getItem("config"));
} catch (errore) {
console.warn("Configurazione corrotta, uso valori predefiniti");
return {
tema: "light",
lingua: "it",
notifiche: true,
};
}
}
Retry Automatico
async function operazioneConRetry(operazione, maxTentativi = 3) {
for (let tentativo = 1; tentativo <= maxTentativi; tentativo++) {
try {
return await operazione();
} catch (errore) {
if (tentativo === maxTentativi) {
throw errore; // Ultimo tentativo fallito
}
console.log(`Tentativo ${tentativo} fallito, riprovo...`);
await new Promise((resolve) => setTimeout(resolve, 1000 * tentativo));
}
}
}
Logging e Monitoraggio
Un sistema di logging efficace è cruciale per identificare e risolvere problemi in produzione:
class ErrorLogger {
static log(errore, contesto = {}) {
const logEntry = {
message: errore.message,
stack: errore.stack,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href,
contesto,
};
// In sviluppo: log in console
if (process.env.NODE_ENV === "development") {
console.error("Errore registrato:", logEntry);
}
// In produzione: invia a servizio di monitoraggio
if (process.env.NODE_ENV === "production") {
this.inviaAServizioMonitoraggio(logEntry);
}
}
static inviaAServizioMonitoraggio(logEntry) {
// Implementazione per servizi come Sentry, LogRocket, etc.
}
}
Gestione Errori Globali
Per catturare errori non gestiti a livello dell’applicazione:
// Per errori sincroni non catturati
window.addEventListener("error", (event) => {
console.error("Errore globale:", event.error);
ErrorLogger.log(event.error, { tipo: "errore_globale" });
});
// Per promise rejections non gestite
window.addEventListener("unhandledrejection", (event) => {
console.error("Promise rejection non gestita:", event.reason);
ErrorLogger.log(new Error(event.reason), { tipo: "promise_rejection" });
event.preventDefault(); // Previene il log automatico in console
});
Best Practices
Fai fallire velocemente: Valida gli input e lancia errori il prima possibile per facilitare il debugging.
Fornisci messaggi di errore significativi: Gli errori dovrebbero spiegare cosa è andato storto e, quando possibile, come risolvere il problema.
Non nascondere gli errori: Anche se gestiti, gli errori dovrebbero essere registrati per analisi future.
Implementa fallback graceful: L’applicazione dovrebbe continuare a funzionare anche quando parti non critiche falliscono.
Testa gli scenari di errore: Scrivi test specifici per verificare che la gestione degli errori funzioni correttamente.
Distingui errori utente da errori sistema: Gli errori causati dall’utente (input non valido) necessitano messaggi diversi da errori tecnici interni.
Una gestione degli errori ben implementata migliora significativamente l’esperienza utente e facilita la manutenzione del codice, trasformando potenziali crash in opportunità per fornire feedback utile e mantenere l’applicazione funzionante.
