Una callback è una funzione che passi a un'altra funzione
In JavaScript le funzioni sono valori. Puoi salvarle in variabili, metterle negli array e, cosa più importante qui, passarle come argomenti. Quando passi una funzione a un'altra funzione perché possa chiamarla più tardi, la funzione che hai passato è una callback.
greet non sa e non gli interessa cosa faccia formatter. Lo chiama con un nome e usa il risultato. Sei tu a scegliere il comportamento passando callback diverse. Questa flessibilità è l'intera ragione d'essere delle callback.
Le callback sincrone vengono eseguite subito
Non tutte le callback sono asincrone. Molti metodi degli array che usi già si basano su callback, e le eseguono in modo sincrono, prima che la chiamata esterna restituisca il risultato:
map, filter e reduce ricevono tutti una callback e la invocano una volta per elemento, in quel preciso momento. Quando map restituisce il risultato, tutte le chiamate alla callback sono già avvenute. Niente viene messo in coda per dopo.
È un semplice pattern di funzione di ordine superiore: "ecco del lavoro, ecco come farlo, dammi il risultato". L'event loop non c'entra.
Le callback asincrone vengono eseguite più tardi
Quando si parla di "callback" di solito si intendono quelle asincrone. Passi una funzione a un'API che richiede tempo (un timer, una richiesta di rete, la lettura di un file) e l'API richiama la tua funzione quando il lavoro è finito.
Ordine dell'output: prima, dopo, poi timer scattato un secondo più tardi. setTimeout non mette in pausa il programma. Consegna la callback al runtime, restituisce subito il controllo e il resto dello script prosegue. Un secondo dopo l'event loop prende la callback e la esegue.
Questa forma "restituisci subito, richiama più tardi" è il modello mentale di ogni API asincrona basata su callback in JavaScript, da addEventListener alle vecchie API dei file di Node.js.
La convenzione error-first (Node.js)
Prima che esistessero le promise, Node.js ha standardizzato una forma precisa per le callback: il primo argomento è un errore (oppure null), gli altri sono il risultato vero e proprio. La troverai ancora nel codice più vecchio e in alcune librerie.
Il chiamante controlla prima err ed esce subito se è truthy. Solo dopo si fida del risultato. È una convenzione, non un obbligo imposto dal linguaggio, ma una volta vista la firma (err, result) => ... la riconoscerai ovunque.
Callback hell
I problemi iniziano quando un passaggio asincrono dipende dal risultato di un altro. Ogni callback deve annidarsi dentro la precedente, e finisci per costruire una scalinata verso destra:
Questa è la famosa "piramide della sventura", o callback hell. Alcune cose la rendono dolorosa:
- Il flusso di controllo va a zigzag invece di leggersi dall'alto in basso.
- Ogni livello ripete lo stesso codice ripetitivo
if (err) return .... - Un'eccezione lanciata in una callback non si propaga a quelle esterne: devi gestire gli errori a ogni livello.
- Fare refactoring significa reindentare tutto il blocco.
Puoi appiattirla un po' estraendo funzioni con un nome, ma il problema di fondo (comporre operazioni asincrone con le callback nude è scomodo) non sparisce. È proprio il problema che le promise sono nate per risolvere.
Due tranelli da conoscere
Non chiamare la callback per sbaglio. Quando passi una callback, passi la funzione stessa, non il risultato della sua chiamata.
Attenzione a this. Se la tua callback è una funzione normale che usa this, il valore di this dipende da come la callback viene chiamata, non da dove è stata definita. Le arrow function aggirano il problema ereditando this dallo scope circostante:
È esattamente per questo motivo che le arrow function sono la scelta predefinita per le callback inline.
Callback e promise a confronto
Le callback compaiono ancora nelle API sincrone (map, forEach, sort), negli event listener (element.addEventListener("click", ...)) e negli hook di basso livello del runtime. Per il lavoro asincrono che produce un solo risultato, l'ecosistema è passato quasi del tutto alle promise.
Il confronto rapido:
- Callback: dirette, minimali, ma si compongono male. La gestione degli errori è manuale a ogni passaggio.
- Promise: un valore che rappresenta un risultato futuro. Le concateni con
.then(), gestisci gli errori una volta sola con.catch()e la piramide si appiattisce.
Dovrai comunque capire le callback: le promise sono costruite sopra di esse e sono ovunque nel codice guidato dagli eventi. Ma oggi capita di rado di scrivere nuove API asincrone con le callback nude.
Prossimo passo: le promise
Le promise prendono l'idea del "fai questo quando quello è pronto" e la racchiudono in un oggetto che puoi passare in giro, concatenare e comporre. È l'argomento della prossima pagina, e il ponte verso async/await, il modo in cui la maggior parte del JavaScript moderno gestisce il lavoro asincrono.
Domande frequenti
Cos'è una funzione callback in JavaScript?
Una callback è una funzione che passi come argomento a un'altra funzione, perché quella possa chiamarla più tardi. setTimeout(() => console.log('hi'), 1000) passa una arrow function come callback: setTimeout la conserva e la chiama quando scatta il timer. Le callback sono il modo originale con cui JavaScript gestiva il 'fai questo quando quello è pronto'.
Che differenza c'è tra callback sincrone e asincrone?
Una callback sincrona viene eseguita subito, durante la chiamata che l'ha ricevuta: [1, 2, 3].map(x => x * 2) invoca la callback tre volte prima che map restituisca il risultato. Una callback asincrona invece viene conservata ed eseguita più tardi, quando si verifica un evento: setTimeout, fs.readFile e gli event listener del DOM funzionano tutti così. Le callback asincrone non bloccano il resto del codice.
Cos'è il callback hell e come si evita?
Il callback hell è la forma a piramide che ottieni quando più callback asincrone dipendono l'una dall'altra e finiscono annidate su diversi livelli. Il flusso di controllo e la gestione degli errori diventano difficili da seguire. La soluzione è usare le promise con catene di .then(), o meglio ancora async/await: entrambe appiattiscono la piramide e rendono il codice di nuovo leggibile.