async - javascript promise




Perché la mia variabile è inalterata dopo averla modificata all'interno di una funzione?-Riferimento di codice asincrono (4)

Dati gli esempi seguenti, perché outerScopeVar è indefinito in tutti i casi?

var outerScopeVar;

var img = document.createElement('img');
img.onload = function() {
    outerScopeVar = this.width;
};
img.src = 'lolcat.png';
alert(outerScopeVar);

var outerScopeVar;
setTimeout(function() {
    outerScopeVar = 'Hello Asynchronous World!';
}, 0);
alert(outerScopeVar);

// Example using some jQuery
var outerScopeVar;
$.post('loldog', function(response) {
    outerScopeVar = response;
});
alert(outerScopeVar);

// Node.js example
var outerScopeVar;
fs.readFile('./catdog.html', function(err, data) {
    outerScopeVar = data;
});
console.log(outerScopeVar);

// with promises
var outerScopeVar;
myPromise.then(function (response) {
    outerScopeVar = response;
});
console.log(outerScopeVar);

// geolocation API
var outerScopeVar;
navigator.geolocation.getCurrentPosition(function (pos) {
    outerScopeVar = pos;
});
console.log(outerScopeVar);

Perché viene emesso undefined in tutti questi esempi? Non voglio soluzioni alternative, voglio sapere perché questo sta accadendo.

Nota: questa è una domanda canonica per l' asincronicità JavaScript . Sentiti libero di migliorare questa domanda e aggiungi altri esempi semplificati con cui la comunità può identificarsi.


Ecco una risposta più concisa per le persone che cercano un riferimento rapido e alcuni esempi che utilizzano promesse e asincroni / attese.

Inizia con l'approccio naive (che non funziona) per una funzione che chiama un metodo asincrono (in questo caso setTimeout ) e restituisce un messaggio:

function getMessage(callback) {
  setTimeout(function() {
    callback('Hello asynchronous world!');
  }, 0);
}
getMessage(function(message) {
  console.log(message);
});

undefined viene registrato in questo caso perché getMessage ritorna prima che venga richiamato il callback setTimeout e aggiorna outerScopeVar .

I due modi principali per risolverlo sono l'uso di callback e promesse :

callback

La modifica qui è che getMessage accetta un parametro di callback che verrà chiamato per restituire i risultati al codice chiamante una volta disponibile.

function getMessage() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve('Hello asynchronous world!');
    }, 0);
  });
}

getMessage().then(function(message) {
  console.log(message);  
});

Promises

Le promesse forniscono un'alternativa più flessibile dei callback perché possono essere combinati in modo naturale per coordinare più operazioni asincrone. Un'implementazione standard Promises/A+ è fornita nativamente in node.js (0.12+) e molti browser correnti, ma è implementata anche in librerie come Bluebird e Q

function getMessage() {
  var deferred = $.Deferred();
  setTimeout(function() {
    deferred.resolve('Hello asynchronous world!');
  }, 0);
  return deferred.promise();
}

getMessage().done(function(message) {
  console.log(message);  
});

jQuery Deferreds

jQuery offre funzionalità simili alle promesse con i suoi differiti.

function getMessage () {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve('Hello asynchronous world!');
        }, 0);
    });
}

async function main() {
    let message = await getMessage();
    console.log(message);
}

main();

async / await

Se il tuo ambiente JavaScript include il supporto per async e attendi (come Node.js 7.6+), allora puoi usare le promesse in modo sincrono all'interno delle funzioni async :

var outerScopeVar;  //line 1
$.post('loldog', function(response) {  //line 2
    outerScopeVar = response;
});
alert(outerScopeVar);  //line 3

In tutti questi scenari outerScopeVar viene modificato o assegnato a un valore in modo asincrono o in un secondo momento (in attesa o in attesa che si verifichi qualche evento), per cui l'esecuzione corrente non attenderà . Così in tutti questi casi i risultati del flusso di esecuzione corrente in outerScopeVar = undefined

Analizziamo ogni esempio (ho contrassegnato la parte chiamata in modo asincrono o ritardata per alcuni eventi):

1.

Qui registriamo un eventlistner che verrà eseguito su quell'evento particolare. Qui il caricamento dell'immagine. Quindi l'esecuzione corrente continua con le righe successive img.src = 'lolcat.png'; e alert(outerScopeVar); nel frattempo l'evento potrebbe non verificarsi. es., funtion img.onload aspetta che l'immagine riferita img.onload caricata, in modo asincrono. Questo succederà a tutti gli esempi seguenti: l'evento potrebbe essere diverso.

2.

Qui l'evento di timeout riproduce il ruolo, che invocherà il gestore dopo il tempo specificato. Qui è 0 , ma continua a registrare un evento asincrono che verrà aggiunto all'ultima posizione della Event Queue per l'esecuzione, il che garantisce il ritardo garantito.

3.

Questa volta, callback ajax.

4.

Il nodo può essere considerato come un re della codifica asincrona. Qui la funzione contrassegnata viene registrata come gestore di richiamata che verrà eseguito dopo aver letto il file specificato.

5.

Una promessa evidente (qualcosa sarà fatto in futuro) è asincrona. vedi Quali sono le differenze tra Differito, Promesso e Futuro in JavaScript?

https://www.quora.com/Whats-the-difference-between-a-promise-and-a-callback-in-Javascript


Per affermare l'ovvio, la coppa rappresenta outerScopeVar .

Le funzioni asincrone sono come ...


Una parola risposta: asincronismo .

prefazioni

Questo argomento è stato ripetuto almeno un paio di migliaia di volte, qui, in . Quindi, per prima cosa vorrei segnalare alcune risorse estremamente utili:

  • @Felix Kling's "Come restituire la risposta da una chiamata AJAX" . Guarda la sua eccellente risposta spiegando i flussi sincroni e asincroni, nonché la sezione "Restructure code".
    @Benjamin Gruenbaum ha anche fatto un sacco di sforzi per spiegare l'asincronicità nello stesso thread.

  • La risposta di @Matt Esch a "Get data from fs.readFile" spiega anche l'asincronicità estremamente bene in un modo semplice.

La risposta alla domanda in questione

Tracciamo prima il comportamento comune. In tutti gli esempi, outerScopeVar viene modificato all'interno di una funzione . Quella funzione non è chiaramente eseguita immediatamente, viene assegnata o passata come argomento. Questo è ciò che chiamiamo un callback .

Ora la domanda è, quando viene chiamata quella richiamata?

Dipende dal caso. Proviamo a rintracciare alcuni comportamenti comuni:

  • img.onload può essere chiamato a volte in futuro , quando (e se) l'immagine è stata caricata correttamente.
  • setTimeout può essere chiamato in futuro , dopo che il ritardo è scaduto e il timeout non è stato cancellato da clearTimeout . Nota: anche se si utilizza 0 come ritardo, tutti i browser hanno un limite di timeout minimo (specificato per essere 4ms nelle specifiche HTML5).
  • Il callback di jQuery $.post può essere chiamato a volte in futuro , quando (e se) la richiesta Ajax è stata completata con successo.
  • Il file fs.readFile di Node.js potrebbe essere chiamato qualche volta in futuro , quando il file è stato letto correttamente o generato un errore.

In tutti i casi, abbiamo una richiamata che potrebbe essere eseguita in futuro . Questo "a volte nel futuro" è ciò che chiamiamo flusso asincrono .

L'esecuzione asincrona viene espulsa dal flusso sincrono. In altre parole, il codice asincrono non verrà mai eseguito mentre è in esecuzione lo stack di codice sincrono. Questo è il significato del fatto che JavaScript sia a thread singolo.

Più specificamente, quando il motore JS è inattivo - non esegue una serie di (a) codice sincrono - interrogherà gli eventi che potrebbero aver attivato richiami asincroni (es. Timeout scaduto, risposta di rete ricevuta) ed eseguirli uno dopo l'altro. Questo è considerato come Event Loop .

Cioè, il codice asincrono evidenziato nelle forme rosse disegnate a mano può essere eseguito solo dopo che tutto il codice sincrono rimanente nei rispettivi blocchi di codice è stato eseguito:

In breve, le funzioni di callback vengono create in modo sincrono ma eseguite in modo asincrono. Non puoi semplicemente fare affidamento sull'esecuzione di una funzione asincrona finché non sai che è stata eseguita e come farlo?

È semplice, davvero. La logica che dipende dall'esecuzione della funzione asincrona dovrebbe essere avviata / chiamata dall'interno di questa funzione asincrona. Ad esempio, spostando anche l' alert s e il file console.log all'interno della funzione di callback, verrà generato il risultato previsto, poiché il risultato è disponibile in quel punto.

Implementazione della propria logica di callback

Spesso è necessario fare più cose con il risultato di una funzione asincrona o fare cose diverse con il risultato, a seconda di dove è stata chiamata la funzione asincrona. Affrontiamo un esempio un po 'più complesso:

var outerScopeVar;
helloCatAsync();
alert(outerScopeVar);

function helloCatAsync() {
    setTimeout(function() {
        outerScopeVar = 'Nya';
    }, Math.random() * 2000);
}

Nota: sto utilizzando setTimeout con un ritardo casuale come funzione asincrona generica, lo stesso esempio si applica ad Ajax, readFile , onload e qualsiasi altro flusso asincrono.

Questo esempio presenta chiaramente lo stesso problema degli altri esempi, non è in attesa del completamento della funzione asincrona.

Affrontiamolo implementando un nostro sistema di callback. Prima di tutto, ci liberiamo di quel brutto outerScopeVar che è completamente inutile in questo caso. Quindi aggiungiamo un parametro che accetta un argomento di funzione, il nostro callback. Al termine dell'operazione asincrona, chiamiamo questa richiamata che passa il risultato. L'implementazione (si prega di leggere i commenti in ordine):

// 1. Call helloCatAsync passing a callback function,
//    which will be called receiving the result from the async operation
helloCatAsync(function(result) {
    // 5. Received the result from the async function,
    //    now do whatever you want with it:
    alert(result);
});

// 2. The "callback" parameter is a reference to the function which
//    was passed as argument from the helloCatAsync call
function helloCatAsync(callback) {
    // 3. Start async operation:
    setTimeout(function() {
        // 4. Finished async operation,
        //    call the callback passing the result as argument
        callback('Nya');
    }, Math.random() * 2000);
}

Snippet di codice dell'esempio precedente:

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);

Molto spesso in casi di utilizzo reali, l'API DOM e la maggior parte delle librerie forniscono già la funzionalità di callback (l'implementazione helloCatAsync in questo esempio dimostrativo). È sufficiente passare la funzione di callback e capire che verrà eseguita al di fuori del flusso sincrono e ristrutturare il codice per adattarlo.

Si noterà inoltre che, a causa della natura asincrona, è impossibile return un valore da un flusso asincrono al flusso sincrono in cui è stato definito il callback, poiché i callback asincroni vengono eseguiti molto tempo dopo il completamento del codice sincrono.

Invece di return un valore da una richiamata asincrona, sarà necessario fare uso del pattern di callback, o ... Promises.

promesse

Sebbene ci siano modi per tenere a bada l' invocazione del callback con JS vaniglia, le promesse stanno aumentando di popolarità e sono attualmente standardizzate in ES6 (vedi Promise - MDN ).

Promises (alias Futures) forniscono una lettura più lineare e quindi piacevole del codice asincrono, ma la spiegazione dell'intera funzionalità è fuori dallo scopo di questa domanda. Invece, lascerò queste eccellenti risorse per gli interessati:

Più materiale di lettura sull'asincronicità di JavaScript

Nota: ho contrassegnato questa risposta come Wiki della comunità, quindi chiunque abbia almeno 100 reputazioni può modificarlo e migliorarlo! Non esitare a migliorare questa risposta o a inviare una risposta completamente nuova, se lo desideri.

Voglio trasformare questa domanda in un argomento canonico per rispondere a problemi di asincronicità che non sono collegati ad Ajax (c'è come restituire la risposta da una chiamata AJAX? ), Quindi questo argomento ha bisogno del tuo aiuto per essere il più utile e utile possibile !







asynchronous