La risposta di Fabrício è perfetta; ma volevo integrare la sua risposta con qualcosa di meno tecnico, che si concentra su un'analogia per aiutare a spiegare il concetto di asincronicità .
Un'analogia ...
Ieri, il lavoro che stavo svolgendo ha richiesto alcune informazioni da un collega. L'ho chiamato; ecco come è andata la conversazione:
Io : Ciao Bob, ho bisogno di sapere come siamo andati al bar la scorsa settimana. Jim vuole un rapporto su di esso, e tu sei l'unico a conoscerne i dettagli.
Bob : Certo, ma mi ci vorranno circa 30 minuti?
Io : È grandioso Bob. Dammi un anello quando hai le informazioni!
A questo punto, ho riagganciato il telefono. Dato che avevo bisogno di informazioni da Bob per completare il mio rapporto, ho lasciato il rapporto e invece sono andato a prendere un caffè, poi ho ricevuto qualche email. 40 minuti dopo (Bob è lento), Bob ha richiamato e mi ha dato le informazioni di cui avevo bisogno. A questo punto, ho ripreso il mio lavoro con il mio rapporto, poiché avevo tutte le informazioni di cui avevo bisogno.
Immagina se la conversazione fosse andata invece così;
Io : Ciao Bob, ho bisogno di sapere come siamo andati al bar la scorsa settimana. Jim vuole che ci sia un rapporto, e tu sei l'unico a conoscerne i dettagli.
Bob : Certo, ma mi ci vorranno circa 30 minuti?
Io : È grandioso Bob. Aspetterò.
E mi sono seduto lì e ho aspettato. E ho aspettato. E ho aspettato. Per 40 minuti Non fare altro che aspettare. Alla fine, Bob mi ha dato le informazioni, abbiamo riattaccato e ho completato il mio rapporto. Ma avevo perso 40 minuti di produttività.
Questo è un comportamento asincrono vs. sincrono
Questo è esattamente ciò che sta accadendo in tutti gli esempi della nostra domanda. Il caricamento di un'immagine, il caricamento di un file dal disco e la richiesta di una pagina tramite AJAX sono operazioni lente (nel contesto dell'informatica moderna).
Anziché attendere il completamento di queste operazioni lente, JavaScript consente di registrare una funzione di richiamata che verrà eseguita al termine dell'operazione lenta. Nel frattempo, tuttavia, JavaScript continuerà a eseguire altro codice. Il fatto che JavaScript esegua altro codice durante l'attesa del completamento dell'operazione lenta rende il comportamento asincrono . Se JavaScript avesse atteso il completamento dell'operazione prima di eseguire qualsiasi altro codice, si sarebbe trattato di un comportamento sincrono .
var outerScopeVar;
var img = document.createElement('img');
// Here we register the callback function.
img.onload = function() {
// Code within this function will be executed once the image has loaded.
outerScopeVar = this.width;
};
// But, while the image is loading, JavaScript continues executing, and
// processes the following lines of JavaScript.
img.src = 'lolcat.png';
alert(outerScopeVar);
Nel codice sopra, chiediamo il caricamento di JavaScript lolcat.png
, che è un'operazione sloooow . La funzione di callback verrà eseguita una volta eseguita questa operazione lenta, ma nel frattempo JavaScript continuerà a elaborare le successive righe di codice; vale a dire alert(outerScopeVar)
.
Questo è il motivo per cui vediamo l'avviso che mostra undefined
; poiché alert()
viene elaborato immediatamente, anziché dopo che l'immagine è stata caricata.
Per correggere il nostro codice, tutto ciò che dobbiamo fare è spostare il alert(outerScopeVar)
codice nella funzione di richiamata. Di conseguenza, non abbiamo più bisogno della outerScopeVar
variabile dichiarata come variabile globale.
var img = document.createElement('img');
img.onload = function() {
var localScopeVar = this.width;
alert(localScopeVar);
};
img.src = 'lolcat.png';
Vedrai sempre che un callback è specificato come una funzione, perché questo è l'unico modo * in JavaScript per definire un codice, ma non eseguirlo fino a dopo.
Pertanto, in tutti i nostri esempi, function() { /* Do something */ }
è il callback; per correggere tutti gli esempi, tutto ciò che dobbiamo fare è spostare lì il codice che richiede la risposta dell'operazione!
* Tecnicamente puoi anche usare eval()
, ma eval()
è male per questo scopo
Come faccio ad aspettare il mio interlocutore?
Al momento potresti avere un codice simile a questo;
function getWidthOfImage(src) {
var outerScopeVar;
var img = document.createElement('img');
img.onload = function() {
outerScopeVar = this.width;
};
img.src = src;
return outerScopeVar;
}
var width = getWidthOfImage('lolcat.png');
alert(width);
Tuttavia, ora sappiamo che ciò return outerScopeVar
accade immediatamente; prima che la onload
funzione di callback abbia aggiornato la variabile. Questo porta al getWidthOfImage()
ritorno undefined
e undefined
all'allerta.
Per risolvere questo problema, dobbiamo consentire alla funzione che chiama getWidthOfImage()
di registrare un callback, quindi spostare l'avviso della larghezza in modo tale da rientrare in quel callback;
function getWidthOfImage(src, cb) {
var img = document.createElement('img');
img.onload = function() {
cb(this.width);
};
img.src = src;
}
getWidthOfImage('lolcat.png', function (width) {
alert(width);
});
... come prima, nota che siamo stati in grado di rimuovere le variabili globali (in questo caso width
).