Callback e Callback Hell: il problema e le soluzioni
Error-first callback, pyramid of doom, perché diventano un problema
Una callback è una funzione passata come argomento a un'altra funzione, per essere eseguita dopo: spesso dopo un'operazione asincrona come una lettura da file o una richiesta di rete. Da sola, un callback non è un problema: lo diventa quando più operazioni dipendenti si accumulano una dentro l'altra.
Una callback singola è come chiedere a qualcuno «quando hai finito, chiamami». Perfettamente ragionevole. Il problema nasce se, dentro quella chiamata, gli dici anche «e quando ti richiamo, tu chiama un altro, che a sua volta ne chiamerà un altro ancora»: una catena di favori annidati che, dopo il quarto anello, nessuno riesce più a seguire con chiarezza.
1. La convenzione error-first: l'errore va sempre per primo
Una convenzione diffusa (nata in Node.js, ma comune in tutto l'ecosistema JavaScript) vuole che un callback riceva l'errore come primo argomento, il risultato come secondo.
function leggiFile(percorso, callback) {
// ... operazione asincrona ...
callback(errore, contenuto);
}
leggiFile('dati.txt', function (errore, contenuto) {
if (errore) {
console.error('Qualcosa e\' andato storto:', errore);
return;
}
console.log(contenuto);
});
Il motivo di questo ordine, non casuale: mettere l'errore per primo costringe chi scrive il callback a pensarci subito, invece di poter scrivere comodamente function(contenuto) {} e dimenticarsi della gestione errori.

2. Il problema: la piramide che cresce verso destra
Quando più operazioni asincrone dipendono l'una dal risultato dell'altra, le callback si annidano, e il codice cresce verso destra a ogni passo, invece che verso il basso.
doSomething(function (result) {
doSomethingElse(result, function (newResult) {
doThirdThing(newResult, function (finalResult) {
console.log('Risultato finale:', finalResult);
}, gestisciErrore);
}, gestisciErrore);
}, gestisciErrore);
Questa forma ha un nome informale ma diffusissimo: «callback hell» o «pyramid of doom», proprio per la forma triangolare che assume l'indentazione crescente.
3. Perché è un problema reale, non solo estetico
Gestione errori ripetuta: ogni livello annidato richiede il proprio controllo d'errore, come si vede sopra, con gestisciErrore passato a ogni chiamata separatamente.
Difficile aggiungere uno step: inserire una nuova operazione nel mezzo della catena significa riscrivere l'indentazione di tutto ciò che segue.
Difficile seguire il flusso: il codice non si legge più «dall'alto in basso» in modo naturale, bisogna seguire mentalmente ogni livello di annidamento per capire l'ordine reale delle operazioni.
4. La soluzione: appiattire con le Promise
Le Promise risolvono proprio questo difetto fondamentale della piramide di callback, catturando tutti gli errori, comprese le eccezioni lanciate, in un unico punto centrale.
doSomething()
.then((result) => doSomethingElse(result))
.then((newResult) => doThirdThing(newResult))
.then((finalResult) => {
console.log('Risultato finale:', finalResult);
})
.catch((errore) => {
console.error('Qualcosa e\' andato storto:', errore);
});
Un solo .catch() alla fine gestisce l'errore, qualunque sia lo step in cui si è verificato: niente più ripetizione, niente più piramide. MDN raccomanda inoltre di mantenere le catene di Promise piatte, senza annidarle: l'annidamento nelle Promise è quasi sempre un segno di composizione poco curata, non una necessità.
In sintesi
I callback non sono «sbagliati»: restano il meccanismo di base su cui poggia tutto il resto, comprese le Promise stesse. Il problema nasce solo quando le dipendenze tra operazioni asincrone si accumulano senza uno strumento per appiattirle. Le Promise, e poi async/await costruito sopra di esse, sono quello strumento.
Commenti (0)
Registrati per commentare, fare domande e proporre l'argomento della prossima pillola (oppure accedi se hai già un account).
Nessun commento. Sii il primo a scrivere qualcosa.