Callback in JavaScript: coordinare operazioni asincrone con funzioni di ritorno

by Anastasia P.
SHARE
Callback in JavaScript: coordinare operazioni asincrone con funzioni di ritorno
© Guida-HTML5.it

Introduzione

In JavaScript, una callback è una funzione passata come argomento a un’altra funzione, con l’obiettivo di essere eseguita in un momento successivo. Questo meccanismo è fondamentale nella programmazione asincrona, perché consente di avviare un’operazione e continuare l’esecuzione senza bloccare il thread principale.

Un uso molto pratico e spesso sottovalutato delle callback è la personalizzazione del comportamento di un’operazione asincrona: ad esempio, decidere cosa fare quando un download termina, come trasformare i dati ricevuti, oppure come aggiornare l’interfaccia utente in base al risultato. In altre parole, le callback non servono solo a “far partire qualcosa dopo”, ma anche a rendere il codice più flessibile e riutilizzabile.

In questo tutorial vedremo un caso concreto: costruire una piccola funzione asincrona che simula il caricamento di un profilo utente e usa callback diverse per gestire il risultato in modi differenti. È un approccio utile quando vuoi separare la logica di esecuzione dalla logica di reazione all’evento.

Codice completo

// Simula una richiesta asincrona per recuperare un profilo utente
function fetchUserProfile(userId, onSuccess, onError) {
  console.log("Avvio recupero profilo per l´utente:", userId);

  // Simuliamo una latenza di rete
  setTimeout(() => {
    // Caso di errore simulato
    if (userId === null || userId === undefined) {
      onError(new Error("ID utente non valido"));
      return;
    }

    // Dati fittizi restituiti dal "server"
    const userProfile = {
      id: userId,
      name: "Giulia Rossi",
      role: "Frontend Developer",
      active: true
    };

    onSuccess(userProfile);
  }, 1200);
}

// Callback per gestire il successo
function renderProfile(profile) {
  console.log("Profilo ricevuto:", profile);

  const status = profile.active ? "attivo" : "non attivo";
  console.log(
    `Ciao ${profile.name}, ruolo: ${profile.role}, stato account: ${status}`
  );
}

// Callback per gestire l´errore
function showError(error) {
  console.error("Errore durante il recupero del profilo:", error.message);
}

// Callback riutilizzabile per trasformare i dati prima di mostrarli
function formatProfile(profile) {
  return {
    ...profile,
    name: profile.name.toUpperCase(),
    role: profile.role.toLowerCase()
  };
}

// Esempio 1: uso standard con callback di successo ed errore
fetchUserProfile(42, (profile) => {
  const formatted = formatProfile(profile);
  renderProfile(formatted);
}, showError);

// Esempio 2: errore gestito dalla callback di errore
fetchUserProfile(null, renderProfile, showError);

// Esempio 3: callback diversa per cambiare il comportamento finale
fetchUserProfile(7, (profile) => {
  console.log("Ricevuto profilo grezzo:", profile);
  console.log("Solo il nome utente:", profile.name);
}, showError);

Spiegazione

La funzione fetchUserProfile simula una richiesta asincrona. Riceve tre parametri:

  • userId: l’identificativo dell’utente da recuperare.
  • onSuccess: callback eseguita quando i dati sono disponibili.
  • onError: callback eseguita in caso di problema.

All’interno della funzione usiamo setTimeout per simulare il ritardo tipico di una chiamata di rete. Dopo 1200 millisecondi, il codice decide se procedere con successo o con errore.

Se userId non è valido, viene invocata la callback onError con un oggetto Error. Questo è importante perché permette al chiamante di decidere come reagire: mostrare un messaggio, fare un retry, registrare un log o interrompere il flusso.

Se invece tutto va bene, la funzione costruisce un oggetto userProfile e lo passa alla callback onSuccess. Qui entra in gioco il vantaggio principale delle callback: la funzione asincrona non decide cosa fare dei dati, ma delega questa responsabilità al chiamante.

Nel nostro esempio abbiamo definito tre funzioni esterne:

  • renderProfile: mostra i dati del profilo in console.
  • showError: gestisce gli errori in modo centralizzato.
  • formatProfile: trasforma i dati prima della visualizzazione.

Questo dimostra un pattern molto utile: una callback può essere usata non solo come “azione finale”, ma anche come punto di estensione del comportamento. Ad esempio, la stessa funzione fetchUserProfile può servire in contesti diversi: una pagina di amministrazione, una dashboard o un componente UI, semplicemente cambiando le callback passate come argomento.

Nel terzo esempio, la callback di successo non stampa il profilo completo, ma solo il nome. Questo evidenzia un concetto importante: la callback rappresenta il contratto di comportamento. La funzione asincrona fornisce il dato, mentre la callback decide come interpretarlo.

Un altro vantaggio è la separazione delle responsabilità. La funzione che recupera i dati non contiene logica di presentazione. Questo rende il codice più testabile, più riutilizzabile e più facile da mantenere.

Best practice

  • Usa callback con nomi chiari: onSuccess, onError, onComplete sono più leggibili di nomi generici come cb.
  • Valida sempre gli argomenti: se una callback non è una funzione, il codice può fallire in modo difficile da diagnosticare.
  • Separa il recupero dati dalla logica di presentazione: la funzione asincrona dovrebbe concentrarsi sul lavoro, non sull’output.
  • Gestisci gli errori in modo esplicito: evita di ignorare i casi falliti, soprattutto nelle operazioni asincrone.
  • Evita callback troppo annidate: se il flusso diventa complesso, valuta promesse o async/await.
  • Riutilizza callback piccole e specializzate: una funzione breve è più facile da testare e da combinare con altre.

Un dettaglio importante: quando usi callback come punti di estensione, cerca di mantenere una firma coerente. Per esempio, se una funzione chiama onSuccess(data), tutte le callback di successo dovrebbero aspettarsi un dato simile. Questo riduce errori e rende il codice prevedibile.

Riepilogo

Le callback sono uno strumento essenziale nella programmazione asincrona in JavaScript. In questo tutorial abbiamo visto un utilizzo molto pratico: coordinare un’operazione asincrona e delegare il comportamento finale a funzioni esterne.

Il punto chiave è che una callback non serve solo a “eseguire qualcosa dopo”, ma anche a personalizzare il risultato di un processo asincrono. Questo approccio migliora la riusabilità, la modularità e la chiarezza del codice.

Se il tuo progetto cresce e la logica asincrona diventa più articolata, le callback restano utili, ma possono diventare difficili da gestire quando il flusso si complica. In quel caso, il passo naturale è passare a promesse o async/await. Tuttavia, comprendere bene le callback è ancora oggi fondamentale per leggere, scrivere e mantenere codice JavaScript moderno.

Approfondisci con risorse ufficiali

  • MDN Web Docs - Callback function: documentazione chiara e completa sul concetto di callback in JavaScript.
  • MDN Web Docs - setTimeout(): utile per capire esempi asincroni basati su timer.
  • MDN Web Docs - Error: approfondimento sulla gestione degli oggetti errore.
  • ECMAScript Language Specification: riferimento ufficiale del linguaggio JavaScript.
  • Node.js Documentation: molto utile per vedere callback applicate a I/O, filesystem ed eventi.

SHARE