Introduzione
Le callback sono funzioni passate come argomento a un’altra funzione, così da poter eseguire del codice quando un’operazione termina. Questo meccanismo è alla base di molte API JavaScript, soprattutto quelle che lavorano in modo asincrono: timer, lettura di file, richieste di rete e gestione di eventi.
Un’esigenza molto comune nelle applicazioni reali è la cancellazione di un’operazione già avviata. Immaginiamo una ricerca con suggerimenti automatici: l’utente digita rapidamente più caratteri e ogni digitazione avvia una nuova richiesta. Se le richieste precedenti non vengono annullate, il browser può eseguire lavoro inutile e una risposta vecchia potrebbe sovrascrivere quella più recente.
In questo tutorial costruiremo un semplice sistema basato su callback e su un oggetto di cancellazione. L’esempio simulerà un caricamento asincrono con setTimeout, ma lo stesso schema può essere applicato a timer, letture locali o altre API che prevedono una callback.
Codice completo
// Crea un´operazione asincrona cancellabile.
// La callback segue la convenzione (errore, risultato).
function avviaCaricamento(nome, callback) {
let completata = false;
let timer;
// Funzione interna che impedisce di eseguire
// la callback più di una volta.
function completa(errore, risultato) {
if (completata) {
return;
}
completata = true;
clearTimeout(timer);
callback(errore, risultato);
}
// Simuliamo un´operazione asincrona, ad esempio una richiesta HTTP.
timer = setTimeout(function () {
const dati = {
nome: nome,
valore: Math.floor(Math.random() * 100)
};
completa(null, dati);
}, 2000);
// Restituiamo un metodo di cancellazione.
return {
annulla: function () {
if (completata) {
return;
}
completa(
new Error("Operazione annullata dall´utente"),
null
);
}
};
}
// Esempio di utilizzo.
const operazione = avviaCaricamento("profilo", function (errore, dati) {
if (errore) {
console.log("Esito:", errore.message);
return;
}
console.log("Dati ricevuti:", dati);
});
// In questo esempio l´operazione viene annullata
// prima che trascorrano i due secondi.
setTimeout(function () {
operazione.annulla();
}, 1000); Spiegazione
La callback con due parametri
Nel codice viene utilizzata una convenzione molto diffusa nel mondo Node.js: la callback riceve prima l’errore e poi il risultato. Se l’operazione termina correttamente, il primo parametro vale null e il secondo contiene i dati:
callback(null, dati); Se invece l’operazione viene annullata, la callback riceve un oggetto Error e un risultato nullo:
callback(new Error("Operazione annullata"), null); Questa struttura rende esplicito l’esito dell’operazione e consente a chi utilizza la funzione di distinguere tra successo e annullamento.
Perché usare una funzione completa
La funzione completa centralizza la conclusione dell’operazione. Sia il timer sia il metodo annulla passano da questa funzione. La variabile completata evita che la callback venga invocata due volte.
Questo controllo è importante perché possono verificarsi condizioni concorrenti. Per esempio, il timer potrebbe terminare nello stesso momento in cui il chiamante richiede la cancellazione. Senza una protezione, il programma potrebbe prima comunicare l’annullamento e subito dopo consegnare anche un risultato valido.
Il valore restituito dalla funzione
La funzione avviaCaricamento restituisce un oggetto con il metodo annulla. Il chiamante conserva questo oggetto e può interrompere il lavoro quando non ne ha più bisogno:
const controllo = avviaCaricamento("dati", callback);
// Più avanti, se necessario:
controllo.annulla(); Questo approccio separa bene due responsabilità: la funzione avvia e gestisce l’operazione, mentre il chiamante decide se mantenerla attiva oppure interromperla.
Un esempio pratico: ricerca con suggerimenti
Possiamo usare lo stesso schema per simulare un campo di ricerca. Ogni nuova ricerca annulla quella precedente, evitando che una risposta lenta aggiorni l’interfaccia con dati obsoleti.
let ricercaAttiva = null;
function cerca(testo) {
if (ricercaAttiva !== null) {
ricercaAttiva.annulla();
}
ricercaAttiva = avviaCaricamento(testo, function (errore, risultati) {
if (errore) {
console.log("Ricerca ignorata:", errore.message);
return;
}
console.log("Risultati per:", risultati.nome);
});
}
cerca("java");
setTimeout(function () {
cerca("javascript");
}, 500); Dopo mezzo secondo, la ricerca di java viene annullata e rimane attiva soltanto quella per javascript.
Best practice
- Invoca la callback una sola volta: usa una variabile di stato o una funzione centralizzata per impedire doppie notifiche.
- Documenta il contratto della callback: specifica l’ordine dei parametri e il significato di
null, errore e risultato. - Rendi la cancellazione idempotente: chiamare
annulla()più volte non dovrebbe generare eccezioni né effetti inattesi. - Pulisci le risorse: cancella timer, listener o riferimenti non più necessari per ridurre il consumo di memoria.
- Ignora i risultati obsoleti: nelle interfacce dinamiche conserva un riferimento all’operazione corrente e annulla quella precedente.
- Non nascondere l’annullamento: comunica chiaramente al chiamante che l’operazione è stata interrotta, utilizzando un oggetto
Erroro uno stato dedicato. - Evita callback troppo annidate: incapsula la logica ripetuta in funzioni nominate e mantieni il flusso leggibile.
È inoltre importante distinguere tra cancellazione reale e semplice ignoramento del risultato. In questo esempio clearTimeout interrompe il timer. In altre API potrebbe non essere possibile fermare concretamente il lavoro: in quel caso si può comunque impostare un flag e impedire l’aggiornamento dell’interfaccia quando arriva una risposta ormai inutile.
Riepilogo
Le callback non servono soltanto a ricevere il risultato di un’operazione asincrona. Con una piccola astrazione possono anche esporre un meccanismo di controllo, come la cancellazione.
Lo schema principale è composto da quattro elementi:
- una funzione che avvia il lavoro asincrono;
- una callback chiamata al termine;
- un riferimento allo stato di completamento;
- un metodo
annullarestituito al chiamante.
Questo modello è particolarmente utile per ricerche dinamiche, anteprime, timer e caricamenti che possono diventare inutili prima della conclusione. La regola più importante è garantire che la callback venga eseguita una sola volta e che l’operazione cancellata non modifichi più lo stato dell’applicazione.
