Abbiamo detto che le funzioni in JavaScript sono oggetti di prima classe, possono quindi essere passate come parametri di un’altra funzione ed anche restituite come risultato.
Dal momento che una funzione è un oggetto è possibile restituirla come valore di ritorno dell’esecuzione di un’altra funzione.
var incrementatore = function(incremento) { return function(valore) { return incremento + valore; }; }; var incremetaDiCinque = incrementatore(5); incrementaDiCinque(10);
Alla variabile "incrementatore" viene assegnata una funzione che restituisce un’altra funzione. La funzione restituita incrementa il suo argomento con l’argomento della funzione che la restituisce.
In altre parole, la funzione incrementatore genera una funzione che incrementa un valore numerico di un numero predefinito.
Riprendiamo le ultime righe del codice qui sopra, ad esempio, se vogliamo creare una funzione che incrementa di cinque il valore passato come parametro possiamo farlo in questo modo:
var incremetaDiCinque = incrementatore(5);
A questo punto possiamo utilizzare la funzione incrementaDiCinque() per aumentare di cinque unità il valore passato come parametro:
document.write(incrementaDiCinque(10)); // risultato 15 document.write(incrementaDiCinque(20)); // risultato 25
Come già ribadito, le funzioni JavaScript sono oggetti possono essere quindi passate come parametri in un’altra funzione.
La funzione passata come parametro è detta generalmente funzione di callback o semplicemente callback.
Si parla quindi di funzione di callback quando una funzione è trasmessa a un’altra funzione come parametro.
Se non fossero esistite ed avessimo avuto l'esigenza di eseguire una altra funzione all'interno di una nuova funzione, essendo blocchi a se stanti, senza le funzioni di callback, non sarebbe stato possibile.
Trovano adesso spiegazione i concetti di Closure, Fuction scope.
Note: Si ricorre spesso a queste funzioni per librerie e framework come le applicazioni per JavaScript jQuery, Angular oppure node.js che rendono le funzioni estendibili e permettono di visualizzarle con input o in circostanze precise.
function secondaria(y){ return y * y; } function principale(x, callback){ return x + callback(5); } var risultato = principale(5,secondaria); document.write(risultato); // risultato 30
Nell' esempio precedente abbiamo creato per prima una funzione "secondaria", che come vedremo sarà la funzione da passare alla funzione "principale":
function secondaria(y){ return y * y; }
Successivamente creiamo un gancio all'interno della funzione "principale, inserendo tra gli argomenti, l'argomento "callback"; l'argomento è cosi chiamato per convenzione ma può assumere altri nomi.
function principale(x, callback){ ... }
All'interno del corpo della funzione "principale" richiamiamo il gancio della funzione callback passandogli anche un parametro
return x + callback(5);
Infine al momento dell'invocazione della funzione "principale", la cui struttura è ora pronta ad accettare tra gli argomenti una funzione secondaria, indichiamo tra essi il nome della funzione "secondaria"
var risultato = principale(5,secondaria);
Per comprendere il corretto funzionamento di una funzione di callback ma non ne dimostra realmente le potenzialità; per rendere il tutto più dinamico riscriviamo il precedente codice prevedendo un secondo parametro "y" che sarà passato attraverso la funzione principale anche alla sua funzione secondaria:
function secondaria(x){ return x * x; } function principale(x, y, callback){ return x + callback(y); } var risultato = principale(5,10,secondaria); document.write(risultato);
Come già detto prima, utilizzare il nome callback è una convenzione tra sviluppatori ma nulla ci impedisce di utilizzare il nome stesso della funzione da inglobare:
function secondaria(x){ return x * x; } function principale(x, y, secondaria){ return x + secondaria(y); } var risultato = principale(5,10,secondaria); document.write(risultato);
E' buona prassi quella di accertarsi, attraverso il costrutto if, che venga passata effettivamente una funzione prima di invocarla:
function secondaria(x){ return x + 5; } function principale(x, y, secondaria){ if (secondaria && typeof secondaria === "function") { return x + secondaria(y); } var risultato = principale(5,10,secondaria); document.write("<br>" + risultato);
Stiamo quindi verificando, prima, che sia presente il parametro "secondaria" (funzione di callback) e successivamente, con la parola chiave "typeof", che essa sia effettivamente una funzione.
L’interpretazione del codice JavaScript da parte di un engine avviene in uno specifico contesto di esecuzione, cioè in un ambiente in cui vengono risolti i riferimenti alle variabili. Un contesto di esecuzione è composto dall’insieme delle variabili accessibili in un dato momento dell’esecuzione di un’istruzione, compresi gli eventuali argomenti di una funzione e l’oggetto this.
Nell'utilizzo delle callback i riferimenti di this possono variare, prendiamo il seguente oggetto per esempio:
var persona = { nome: "Mario", cognome: "Rossi", mostraNomeCompleto: function () { return this.nome + " " + this.cognome; } };
Creiamo ora una funzione che avrà come parametro il metodo/funzione "mostraNomeCompleto" dell'oggetto "persona":
function saluta(callback) { return "Buongiorno " + callback(); } saluta(persona.mostraNomeCompleto);
Il risultato sarà "Buongiorno undefined undefined";
Il problema nasce dal fatto che la funzione di callback mostraNomeCompleto() è eseguita nel contesto di esecuzione della funzione saluta().
Non stiamo invocando il metodo mostraNomeCompleto() ma lo passiamo alla funzione saluta().
All'interno della funzione saluta() il metodo mostraNomeCompleto() è si invocato ma è allo stesso tempo al di fuori del suo contesto di esecuzione;
this non rappresenterà più l’oggetto persona ma l’oggetto globale.
Note: in un browser l'oggetto globale corrisponde alla finestra corrente (window).
Per risolvere il problema possiamo fare ricorso ai metodi dell’oggetto function che ci consentono di specificare a cosa associare la parola chiave this.
.call() | Permette di invocare una funzione impostando il primo parametro come oggetto di riferimento per this |
.apply() | Simile a .call() prevede due parametri: il primo è l’oggetto da associare a this mentre il secondo parametro è un array dei valori da passare alla funzione |
.blind() | Permette di indicare al momento dell'invocazione il this di riferimento |
Il metodo call() permette di invocare una funzione (di callback) indicando come primo parametro di riferimento per this ed per i parametri successivi i valori da passare alla funzione.
function saluta(callback) { return "Buongiorno " + callback.call(persona, nome, cognome); } saluta(persona.mostraNomeCompleto);
Il secondo metodo apply() è del tutto simile al metodo call() con la differenza che prevede due soli parametri: il primo è l’oggetto da associare a this mentre il secondo parametro è un array dei valori da passare alla funzione da invocare
var persona = { dati: ["Mario", "Rossi"]; mostraNomeCompleto: function () { return this.dati[0] + " " + this.dati[1]; } };
function saluta(callback) { return "Buongiorno " + callback.apply(persona, dati); } saluta(persona.mostraNomeCompleto);
Con il metodo blind() indichiamo al momento dell'invocazione il this di riferimento tramite passandolo come argomento.
function saluta(callback) { return "Buongiorno " + callback(); } saluta(persona.mostraNomeCompleto.blind(persona));
A differenza di call() e apply(), con blind() la funzione resta identica.
L’uso delle callback è molto frequente nella programmazione JavaScript specie con il metodo forEach() degli array.
var items = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; items.foreach( function(item){ return item++; }
La funzione all'interno del metodo foreach() è una callback;
Come fa javascript a sapere che "item" rappresenta i singoli elementi di un array? di fatto, non lo sa; quel che sa, è che, all'interno della funzione foreach (metodo degli array), si occuperà di ciclare gli elementi dell'array stesso. Il parametro all'interno della funzione di callback avrebbe potuto chiamarsi con un qualunque altro nome e sarebbe funzionato ugualmente.
Note: Asincrono in informatica: modalità di trasmissione dati che non dipende dal compiersi di altri processi.
L'engine di Javascript legge il codice scritto dall'alto verso il basso; ciò significa che sarà eseguito prima il codice posizionato più in alto ed a seguire i successivi.
Lavorando con le funzioni di callback potremmo avere l'esigenza di eseguire prima delle istruzioni prima di altre.
Per fare questo potremo servirci del metodo .setTimeout() dell'oggetto window
window.setTimeout( { ... }, x );
function secondaria(){ return "Io sono la funzione di callback"; } function principale(ritardo, x, callback) { window.setTimeout( function() { document.write("Scusate il ritardo!" + risultato);}, ritardo); return x + callback(); } var risultato = principale(3000, "Ciao da primaria.", secondaria); document.write("<br>" + risultato);
Esaminiamo il codice porzione per porzione:
function secondaria(){ return "Io sono la funzione di callback"; }
Come di consueto creiamo la funzione "secondaria" che sarà la funzione di richiamo (callback) che in questo esempio non ha bisogno di alcun parametro ed eseguirà una semplice stringa "Io sono la funzione di callback";
function principale(ritardo, x, callback) { window.setTimeout( function() { document.write("Scusate il ritardo!" + risultato);}, ritardo); return x + callback(); }
Creiamo la funzione "principale" che ingloberà mediante il parametro/gancio "callback" la funzione "secondaria"; notiamo subito che abbiamo previsto un nuovo parametro, il parametro "ritardo".
All'interno della funzione prinicipale e di windows.setTimeout() abbiamo definito una funzione anonima la quale stamperà la stringa "Scusate il ritardo" + la variabile "risultato", il cui valore non è ancora accessibile/disponibile ma lo sarà solo al termine dell'esecuzione di tutto il blocco del codice d'esempio.
Infine con return "ritorniamo" il risultato della funzione "principale" indicando le operazioni da effettuare incluse quelle con la funzione di callback.
var risultato = principale(3000, "Ciao da primaria.", secondaria); document.write("<br>" + risultato);
Avviamo il tutto. Invochiamo la funzione principale, includendo tra i parametri: tempo di ritrardo (dell'esecuzione blocco windows.setTimeout() e della funzione in essa contenuta), valore variabile x (che necessita alla funzione principale alla riga del comando return) e il nome della funzione di callback da associare alla funzione principale;
Questo il risultato:
Tralasciando la banalità del codice utilizzato per la comprensione del funzionamento, abbiamo "concatenato" ben tre funzioni e ne abbiamo modificato l'ordine di esecuzione.
Note: se non avessimo previsto un ritardo e quindi una modifica nell'ordine di esecuzione la variabile "risultato" nella riga di windows.setTimeout() non sarebbe stata accessibile e ci sarebbe stato restituito errore