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
doubleviene assegnata allamapfunzioni dal chiamante. - La
mapfunzioni deve chiamare la funzionedoublezero 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
bindbindgenera in modo efficace una nuova funzione che impostathisa ciò che è stato passato albindquindi 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
thiscontesto.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
EventListenerfunction 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)