Ricerca…
Esempi di utilizzo di callback semplici
Le callback offrono un modo per estendere la funzionalità di una funzione (o metodo) senza modificarne il codice. Questo approccio è spesso usato nei moduli (librerie / plugin), il cui codice non dovrebbe essere modificato.
Supponiamo di aver scritto la seguente funzione, calcolando la somma di una data matrice di valori:
function foo(array) {
var sum = 0;
for (var i = 0; i < array.length; i++) {
sum += array[i];
}
return sum;
}
Supponiamo ora di voler fare qualcosa con ogni valore dell'array, ad esempio visualizzarlo usando alert()
. Potremmo apportare le modifiche appropriate nel codice di foo
, come questo:
function foo(array) {
var sum = 0;
for (var i = 0; i < array.length; i++) {
alert(array[i]);
sum += array[i];
}
return sum;
}
Ma cosa succede se decidiamo di utilizzare console.log
anziché alert()
? Ovviamente cambiare il codice di foo
, ogni volta che decidiamo di fare qualcos'altro con ogni valore, non è una buona idea. È molto meglio avere l'opzione di cambiare idea senza cambiare il codice di foo
. Questo è esattamente il caso d'uso per i callback. Dobbiamo solo cambiare leggermente la firma e il corpo di foo
:
function foo(array, callback) {
var sum = 0;
for (var i = 0; i < array.length; i++) {
callback(array[i]);
sum += array[i];
}
return sum;
}
E ora siamo in grado di cambiare il comportamento di foo
cambiando semplicemente i suoi parametri:
var array = [];
foo(array, alert);
foo(array, function (x) {
console.log(x);
});
Esempi con funzioni asincrone
In jQuery, il metodo $.getJSON()
per recuperare i dati JSON è asincrono. Pertanto, il codice di passaggio in una richiamata garantisce che il codice venga chiamato dopo che è stato recuperato il JSON.
$.getJSON()
:
$.getJSON( url, dataObject, successCallback );
Esempio di codice $.getJSON()
:
$.getJSON("foo.json", {}, function(data) {
// data handling code
});
Quanto segue non funzionerebbe, perché il codice per la gestione dei dati verrebbe probabilmente chiamato prima che i dati vengano effettivamente ricevuti, poiché la funzione $.getJSON
richiede un intervallo di tempo non specificato e non regge lo stack di chiamate mentre attende il JSON.
$.getJSON("foo.json", {});
// data handling code
Un altro esempio di una funzione asincrona è la funzione animate()
di jQuery. Poiché richiede un tempo specifico per eseguire l'animazione, a volte è preferibile eseguire del codice direttamente dopo l'animazione.
sintassi .animate()
:
jQueryElement.animate( properties, duration, callback );
Ad esempio, per creare un'animazione in dissolvenza dopo la quale l'elemento scompare completamente, è possibile eseguire il codice seguente. Notare l'uso del callback.
elem.animate( { opacity: 0 }, 5000, function() {
elem.hide();
} );
Ciò consente di nascondere l'elemento subito dopo che la funzione ha terminato l'esecuzione. Questo differisce da:
elem.animate( { opacity: 0 }, 5000 );
elem.hide();
perché quest'ultimo non aspetta che animate()
(una funzione asincrona) completi, e quindi l'elemento è nascosto subito, producendo un effetto indesiderato.
Cos'è una richiamata?
Questa è una normale chiamata di funzione:
console.log("Hello World!");
Quando si chiama una funzione normale, fa il suo lavoro e quindi restituisce il controllo al chiamante.
Tuttavia, a volte una funzione deve restituire il controllo al chiamante per eseguire il proprio lavoro:
[1,2,3].map(function double(x) {
return 2 * x;
});
Nell'esempio sopra, la funzione double
è una callback per la map
funzioni perché:
- La funzione
double
viene assegnata allamap
funzioni dal chiamante. - La
map
funzioni deve chiamare la funzionedouble
zero o più volte per fare il suo lavoro.
Pertanto, la map
funzioni restituisce essenzialmente il controllo al chiamante ogni volta che chiama la funzione double
. Da qui il nome "callback".
Le funzioni possono accettare più di una richiamata:
promise.then(function onFulfilled(value) {
console.log("Fulfilled with value " + value);
}, function onRejected(reason) {
console.log("Rejected with reason " + reason);
});
Qui la funzione accetta then
due funzioni di callback, onFulfilled
e onRejected
. Inoltre, solo una di queste due funzioni di callback viene effettivamente chiamata.
La cosa più interessante è che la funzione then
ritorna prima che uno dei callback sono chiamati. Quindi, una funzione di callback può essere chiamata anche dopo che la funzione originale è ritornata.
Continuazione (sincrona e asincrona)
Le callback possono essere utilizzate per fornire il codice da eseguire dopo il completamento di un metodo:
/** * @arg {Function} then continuation callback */ function doSomething(then) { console.log('Doing something'); then(); } // Do something, then execute callback to log 'done' doSomething(function () { console.log('Done'); }); console.log('Doing something else'); // Outputs: // "Doing something" // "Done" // "Doing something else"
Il metodo doSomething()
sopra viene eseguito in modo sincrono con il callback - blocchi di esecuzione fino a quando doSomething()
restituisce, assicurandosi che il callback sia eseguito prima che l'interprete si muova.
Le callback possono anche essere utilizzate per eseguire il codice in modo asincrono:
doSomethingAsync(then) { setTimeout(then, 1000); console.log('Doing something asynchronously'); } doSomethingAsync(function() { console.log('Done'); }); console.log('Doing something else'); // Outputs: // "Doing something asynchronously" // "Doing something else" // "Done"
Le callback then
sono considerate continuazioni dei metodi doSomething()
. Fornire un callback come ultima istruzione in una funzione è chiamato tail-call , che è ottimizzato dagli interpreti ES2015 .
Gestione degli errori e ramificazione del flusso di controllo
Le callback sono spesso utilizzate per fornire la gestione degli errori. Questa è una forma di branching del flusso di controllo, in cui alcune istruzioni vengono eseguite solo quando si verifica un errore:
const expected = true;
function compare(actual, success, failure) {
if (actual === expected) {
success();
} else {
failure();
}
}
function onSuccess() {
console.log('Value was expected');
}
function onFailure() {
console.log('Value was unexpected/exceptional');
}
compare(true, onSuccess, onFailure);
compare(false, onSuccess, onFailure);
// Outputs:
// "Value was expected"
// "Value was unexpected/exceptional"
L'esecuzione di codice in compare()
sopra ha due rami possibili: success
quando i valori attesi e reali sono gli stessi, e error
quando sono diversi. Ciò è particolarmente utile quando il flusso di controllo dovrebbe ramificarsi dopo alcune istruzioni asincrone:
function compareAsync(actual, success, failure) {
setTimeout(function () {
compare(actual, success, failure)
}, 1000);
}
compareAsync(true, onSuccess, onFailure);
compareAsync(false, onSuccess, onFailure);
console.log('Doing something else');
// Outputs:
// "Doing something else"
// "Value was expected"
// "Value was unexpected/exceptional"
Va notato, più callback non devono essere mutuamente esclusivi - entrambi i metodi possono essere chiamati. Allo stesso modo, il compare()
può essere scritto con callback che sono opzionali (usando un noop come valore predefinito - vedi schema Oggetto Nullo ).
Callback e `questo`
Spesso quando si utilizza un callback si desidera accedere a un contesto specifico.
function SomeClass(msg, elem) {
this.msg = msg;
elem.addEventListener('click', function() {
console.log(this.msg); // <= will fail because "this" is undefined
});
}
var s = new SomeClass("hello", someElement);
soluzioni
Usa il
bind
bind
genera in modo efficace una nuova funzione che impostathis
a ciò che è stato passato albind
quindi chiama la funzione originale.function SomeClass(msg, elem) { this.msg = msg; elem.addEventListener('click', function() { console.log(this.msg); }.bind(this)); // <=- bind the function to `this` }
Usa le funzioni freccia
Le funzioni di freccia rilegano automaticamente l'attuale
this
contesto.function SomeClass(msg, elem) { this.msg = msg; elem.addEventListener('click',() => { // <=- arrow function binds `this` console.log(this.msg); }); }
Spesso si desidera chiamare una funzione membro, passando in modo ideale tutti gli argomenti passati all'evento sulla funzione.
soluzioni:
Usa il bind
function SomeClass(msg, elem) { this.msg = msg; elem.addEventListener('click', this.handleClick.bind(this)); } SomeClass.prototype.handleClick = function(event) { console.log(event.type, this.msg); };
Usa le funzioni freccia e l'operatore resto
function SomeClass(msg, elem) { this.msg = msg; elem.addEventListener('click', (...a) => this.handleClick(...a)); } SomeClass.prototype.handleClick = function(event) { console.log(event.type, this.msg); };
In particolare per i listener di eventi DOM è possibile implementare l' interfaccia
EventListener
function SomeClass(msg, elem) { this.msg = msg; elem.addEventListener('click', this); } SomeClass.prototype.handleEvent = function(event) { var fn = this[event.type]; if (fn) { fn.apply(this, arguments); } }; SomeClass.prototype.click = function(event) { console.log(this.msg); };
Richiamata usando la funzione Freccia
L'uso della funzione freccia come funzione di callback può ridurre le linee di codice.
La sintassi predefinita per la funzione freccia è
() => {}
Questo può essere usato come callback
Ad esempio se vogliamo stampare tutti gli elementi in un array [1,2,3,4,5]
senza la funzione freccia, il codice sarà simile a questo
[1,2,3,4,5].forEach(function(x){
console.log(x);
}
Con la funzione freccia, può essere ridotto a
[1,2,3,4,5].forEach(x => console.log(x));
Qui la funzione di function(x){console.log(x)}
callback function(x){console.log(x)}
è ridotta a x=>console.log(x)