Introduzione
Quando un’applicazione elabora molti elementi in sequenza, un singolo errore può interrompere l’intera operazione. Questo comportamento è spesso indesiderato: se stiamo importando cento prodotti, per esempio, il fatto che un solo elemento sia malformato non dovrebbe necessariamente impedire l’elaborazione degli altri novantanove.
In questi casi, try...catch può essere utilizzato all’interno di un ciclo per isolare gli errori relativi a ogni singolo elemento. In questo modo possiamo:
- continuare a elaborare gli elementi validi;
- raccogliere informazioni sugli elementi problematici;
- restituire un riepilogo finale dell’operazione;
- evitare che un errore locale blocchi l’intero batch.
Questa tecnica è particolarmente utile per importazioni di dati, elaborazioni di file, trasformazioni di record e procedure di sincronizzazione. È importante, però, distinguere tra un errore recuperabile relativo a un elemento e un errore grave che rende impossibile proseguire.
Codice completo
const ordini = [
{ id: 101, totale: 49.90, email: "[email protected]" },
{ id: 102, totale: -10, email: "[email protected]" },
{ id: 103, totale: 89.50, email: "indirizzo-non-valido" },
{ id: 104, totale: 120, email: "[email protected]" }
];
function validaOrdine(ordine) {
if (!ordine || typeof ordine !== "object") {
throw new TypeError("L´ordine deve essere un oggetto");
}
if (!Number.isFinite(ordine.totale) || ordine.totale <= 0) {
throw new RangeError(
`Il totale dell´ordine ${ordine.id} non è valido`
);
}
if (
typeof ordine.email !== "string" ||
!ordine.email.includes("@")
) {
throw new Error(
`L´email dell´ordine ${ordine.id} non è valida`
);
}
}
function preparaOrdine(ordine) {
validaOrdine(ordine);
return {
id: ordine.id,
totale: Number(ordine.totale.toFixed(2)),
email: ordine.email.trim().toLowerCase(),
stato: "pronto"
};
}
function elaboraBatch(listaOrdini) {
const elaborati = [];
const scartati = [];
for (const ordine of listaOrdini) {
try {
// L´errore di questo elemento non interrompe il ciclo.
const ordinePreparato = preparaOrdine(ordine);
elaborati.push(ordinePreparato);
} catch (errore) {
// Salviamo il contesto utile per analizzare il problema.
scartati.push({
id: ordine?.id ?? "sconosciuto",
motivo: errore.message
});
}
}
return {
elaborati,
scartati,
totaleRicevuti: listaOrdini.length,
totaleElaborati: elaborati.length,
totaleScartati: scartati.length
};
}
const risultato = elaboraBatch(ordini);
console.log("Elaborazione completata");
console.table(risultato.elaborati);
console.table(risultato.scartati);
console.log(
`Elaborati: ${risultato.totaleElaborati}/` +
`${risultato.totaleRicevuti}`
); Spiegazione
Isolare il singolo elemento
Il punto fondamentale dell’esempio è la posizione del blocco try...catch. Il blocco si trova dentro il ciclo for...of, quindi ogni iterazione possiede una gestione indipendente dell’errore.
Quando preparaOrdine() genera un’eccezione, l’esecuzione passa immediatamente al relativo blocco catch. L’ordine corrente viene inserito nell’array scartati, mentre il ciclo continua con l’elemento successivo.
Se invece mettessimo l’intero ciclo dentro un unico try...catch, il primo errore interromperebbe tutte le iterazioni successive:
try {
for (const ordine of ordini) {
const preparato = preparaOrdine(ordine);
console.log(preparato);
}
} catch (errore) {
console.error("Il batch è stato interrotto:", errore.message);
} Questa seconda soluzione può essere corretta quando il batch deve essere considerato un’operazione indivisibile. Tuttavia, per importazioni indipendenti, è spesso preferibile isolare ogni elemento.
Conservare informazioni utili
Nel blocco catch non limitiamoci a stampare il messaggio nella console. Salviamo anche l’identificativo dell’ordine e il motivo dello scarto. Un messaggio come “errore generico” è poco utile; sapere che l’ordine 102 ha un totale negativo permette di correggere rapidamente il dato.
L’operatore optional chaining (?.) viene utilizzato per leggere in sicurezza l’identificativo anche quando l’oggetto è nullo o non definito. L’operatore nullish coalescing (??) fornisce il valore predefinito "sconosciuto".
Distinguere risultato e scarti
La funzione restituisce un oggetto riepilogativo con due collezioni separate. Questo approccio consente al chiamante di decidere cosa fare dopo:
- salvare gli ordini elaborati;
- mostrare all’utente il numero degli scarti;
- esportare gli elementi problematici;
- richiedere una correzione dei dati.
Inoltre, il risultato contiene i conteggi, utili per controlli automatici e statistiche operative.
Best practice
- Metti il try nel punto più piccolo possibile: includi solo le istruzioni che possono davvero generare l’errore.
- Non ignorare le eccezioni: un blocco
catchvuoto nasconde problemi e rende difficile il debugging. - Conserva il contesto: registra l’identificativo dell’elemento, l’indice o altre informazioni utili.
- Non confondere scarto e successo: un elemento non elaborato deve essere rappresentato chiaramente nel risultato.
- Decidi quando interrompere: se l’errore indica che l’ambiente non è disponibile o che i dati sono inutilizzabili, continuare potrebbe essere rischioso.
- Evita messaggi tecnici per gli utenti finali: conserva il messaggio originale per gli sviluppatori, ma mostra descrizioni comprensibili nell’interfaccia.
- Usa funzioni separate: una funzione per la validazione, una per la trasformazione e una per l’orchestrazione rendono il codice più testabile.
Un ulteriore miglioramento consiste nell’aggiungere un limite agli errori. Se quasi tutti gli elementi vengono scartati, l’elaborazione potrebbe essere considerata fallita e richiedere un intervento manuale.
Riepilogo
Usare try...catch all’interno di un ciclo permette di gestire gli errori elemento per elemento, evitando che un problema locale interrompa un’intera elaborazione. La strategia consiste nel validare ogni record, elaborare quelli corretti e raccogliere in una struttura separata quelli che generano eccezioni.
La scelta tra un unico try...catch esterno e più blocchi interni dipende dal significato dell’operazione. Se ogni elemento è indipendente, l’isolamento per iterazione è generalmente la soluzione più robusta. Se invece tutti gli elementi devono essere elaborati insieme, può essere più appropriato interrompere il batch al primo errore.
