Compte tenu des exemples suivants, pourquoi n'est-il outerScopeVar
pas défini dans tous les cas?
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);
Pourquoi est-il affiché undefined
dans tous ces exemples? Je ne veux pas de solutions de contournement, je veux savoir pourquoi cela se produit.
Remarque: Il s'agit d'une question canonique pour l' asynchronicité JavaScript . N'hésitez pas à améliorer cette question et à ajouter des exemples plus simplifiés auxquels la communauté peut s'identifier.
javascript
asynchronous
Fabrício Matté
la source
la source
Réponses:
Réponse en un mot: asynchronicité .
Préface
Ce sujet a été répété au moins quelques milliers de fois, ici, dans Stack Overflow. Par conséquent, je voudrais tout d'abord souligner quelques ressources extrêmement utiles:
@Felix Kling's answer to "Comment renvoyer la réponse d'un appel asynchrone?" . Voir son excellente réponse expliquant les flux synchrones et asynchrones, ainsi que la section "Code de restructuration".
@Benjamin Gruenbaum a également mis beaucoup d'efforts pour expliquer l'asynchronicité dans le même fil.
La réponse de @Matt Esch à «Récupérer les données de fs.readFile» explique également très bien l'asynchronicité de manière simple.
La réponse à la question posée
Voyons d'abord le comportement commun. Dans tous les exemples, le
outerScopeVar
est modifié à l'intérieur d'une fonction . Cette fonction n'est clairement pas exécutée immédiatement, elle est affectée ou passée en argument. C'est ce que nous appelons un rappel .Maintenant, la question est, quand ce rappel est-il appelé?
Cela dépend du cas. Essayons à nouveau de tracer un comportement courant:
img.onload
peut être appelée dans le futur , lorsque (et si) l'image a été chargée avec succès.setTimeout
peut être appelé dans le futur , une fois le délai expiré et le délai d'attente n'a pas été annulé parclearTimeout
. Remarque: même en cas d'utilisation en0
tant que délai, tous les navigateurs ont un plafond de délai d'expiration minimum (spécifié à 4 ms dans la spécification HTML5).$.post
Le rappel de jQuery peut être appelé dans le futur , lorsque (et si) la requête Ajax a été effectuée avec succès.fs.readFile
peut être appelé dans le futur , lorsque le fichier a été lu avec succès ou a généré une erreur.Dans tous les cas, nous avons un rappel qui peut s'exécuter dans le futur . Ce «dans le futur» est ce que nous appelons le flux asynchrone .
L'exécution asynchrone est poussée hors du flux synchrone. Autrement dit, le code asynchrone ne s'exécutera jamais pendant l'exécution de la pile de code synchrone. C'est la signification de JavaScript étant un thread unique.
Plus spécifiquement, lorsque le moteur JS est inactif - n'exécutant pas une pile de (a) code synchrone - il interrogera les événements qui peuvent avoir déclenché des rappels asynchrones (par exemple, délai d'expiration, réponse du réseau reçue) et les exécutera l'un après l'autre. Ceci est considéré comme une boucle d'événement .
C'est-à-dire que le code asynchrone mis en évidence dans les formes rouges dessinées à la main ne peut s'exécuter qu'après l'exécution de tout le code synchrone restant dans leurs blocs de code respectifs:
En bref, les fonctions de rappel sont créées de manière synchrone mais exécutées de manière asynchrone. Vous ne pouvez tout simplement pas compter sur l'exécution d'une fonction asynchrone jusqu'à ce que vous sachiez qu'elle a été exécutée, et comment faire cela?
C'est simple, vraiment. La logique qui dépend de l'exécution de la fonction asynchrone doit être démarrée / appelée depuis l'intérieur de cette fonction asynchrone. Par exemple, déplacer les
alert
s etconsole.log
s trop à l'intérieur de la fonction de rappel produirait le résultat attendu, car le résultat est disponible à ce stade.Implémentation de votre propre logique de rappel
Souvent, vous devez faire plus de choses avec le résultat d'une fonction asynchrone ou faire différentes choses avec le résultat selon l'endroit où la fonction asynchrone a été appelée. Prenons un exemple un peu plus complexe:
Remarque: J'utilise
setTimeout
avec un retard aléatoire comme fonction générique asynchrone, le même exemple s'applique à AjaxreadFile
,onload
et à tout autre flux asynchrone.Cet exemple souffre clairement du même problème que les autres exemples, il n'attend pas que la fonction asynchrone s'exécute.
Abordons-le en mettant en œuvre notre propre système de rappel. Tout d'abord, nous nous débarrassons de ce vilain
outerScopeVar
qui est complètement inutile dans ce cas. Ensuite, nous ajoutons un paramètre qui accepte un argument de fonction, notre rappel. Lorsque l'opération asynchrone se termine, nous appelons ce rappel en passant le résultat. L'implémentation (veuillez lire les commentaires dans l'ordre):Extrait de code de l'exemple ci-dessus:
Le plus souvent dans des cas d'utilisation réels, l'API DOM et la plupart des bibliothèques fournissent déjà la fonctionnalité de rappel (l'
helloCatAsync
implémentation dans cet exemple démonstratif). Il vous suffit de passer la fonction de rappel et de comprendre qu'elle s'exécutera hors du flux synchrone, et de restructurer votre code pour s'adapter à cela.Vous remarquerez également qu'en raison de la nature asynchrone, il est impossible de
return
renvoyer une valeur d'un flux asynchrone au flux synchrone où le rappel a été défini, car les rappels asynchrones sont exécutés longtemps après que le code synchrone a déjà terminé son exécution.Au lieu d'
return
ingérer une valeur à partir d'un rappel asynchrone, vous devrez utiliser le modèle de rappel, ou ... Promesses.Promesses
Bien qu'il existe des moyens de garder l' enfer de rappel à distance avec vanilla JS, les promesses gagnent en popularité et sont actuellement en cours de standardisation dans ES6 (voir Promise - MDN ).
Promises (aka Futures) offre une lecture plus linéaire et donc agréable du code asynchrone, mais expliquer toute leur fonctionnalité est hors de portée de cette question. Au lieu de cela, je laisse ces excellentes ressources aux intéressés:
Plus de lecture sur l'asynchronicité JavaScript
la source
La réponse de Fabrício est parfaite; mais je voulais compléter sa réponse par quelque chose de moins technique, qui se concentre sur une analogie pour aider à expliquer le concept d'asynchronicité .
Une analogie ...
Hier, le travail que je faisais exigeait des informations d'un collègue. Je l'ai appelé; voici comment s'est déroulée la conversation:
À ce stade, j'ai raccroché le téléphone. Comme j'avais besoin d'informations de Bob pour terminer mon rapport, j'ai quitté le rapport et je suis allé prendre un café à la place, puis j'ai rattrapé un e-mail. 40 minutes plus tard (Bob est lent), Bob a rappelé et m'a donné les informations dont j'avais besoin. À ce stade, j'ai repris mon travail avec mon rapport, car j'avais toutes les informations dont j'avais besoin.
Imaginez si la conversation s'était déroulée comme ça à la place;
Et je me suis assis là et j'ai attendu. Et attendu. Et attendu. Pendant 40 minutes. Ne rien faire mais attendre. Finalement, Bob m'a donné l'information, nous avons raccroché et j'ai terminé mon rapport. Mais j'avais perdu 40 minutes de productivité.
Il s'agit d'un comportement asynchrone ou synchrone
C'est exactement ce qui se passe dans tous les exemples de notre question. Charger une image, charger un fichier sur le disque et demander une page via AJAX sont toutes des opérations lentes (dans le contexte de l'informatique moderne).
Plutôt que d' attendre la fin de ces opérations lentes, JavaScript vous permet d'enregistrer une fonction de rappel qui sera exécutée une fois l'opération lente terminée. Dans l'intervalle, cependant, JavaScript continuera d'exécuter d'autres codes. Le fait que JavaScript exécute un autre code en attendant la fin de l'opération lente rend le comportement asynchrone . Si JavaScript avait attendu la fin de l'opération avant d'exécuter tout autre code, cela aurait été un comportement synchrone .
Dans le code ci-dessus, nous demandons à JavaScript de se charger
lolcat.png
, ce qui est une opération lente . La fonction de rappel sera exécutée une fois cette opération lente terminée, mais en attendant, JavaScript continuera de traiter les lignes de code suivantes; iealert(outerScopeVar)
.C'est pourquoi nous voyons l'alerte apparaître
undefined
; puisque lealert()
est traité immédiatement, plutôt qu'après le chargement de l'image.Afin de corriger notre code, tout ce que nous avons à faire est de déplacer le
alert(outerScopeVar)
code dans la fonction de rappel. Par conséquent, nous n'avons plus besoin de laouterScopeVar
variable déclarée comme variable globale.Vous verrez toujours qu'un rappel est spécifié en tant que fonction, car c'est le seul * moyen en JavaScript pour définir du code, mais ne l'exécutez que plus tard.
Par conséquent, dans tous nos exemples, le
function() { /* Do something */ }
est le rappel; pour corriger tous les exemples, il suffit de déplacer le code qui a besoin de la réponse de l'opération là-dedans!* Techniquement, vous pouvez également l'utiliser
eval()
, mais ileval()
est mauvais à cet effetComment faire attendre mon appelant?
Vous pourriez avoir actuellement un code similaire à celui-ci;
Cependant, nous savons maintenant que cela
return outerScopeVar
se produit immédiatement; avant que laonload
fonction de rappel n'ait mis à jour la variable. Cela conduit àgetWidthOfImage()
revenirundefined
et àundefined
être alerté.Pour résoudre ce problème, nous devons autoriser la fonction appelante
getWidthOfImage()
à enregistrer un rappel, puis déplacer l'alerte de la largeur pour qu'elle soit comprise dans ce rappel;... comme précédemment, notez que nous avons pu supprimer les variables globales (dans ce cas
width
).la source
Voici une réponse plus concise pour les personnes qui recherchent une référence rapide ainsi que quelques exemples utilisant promesses et async / wait.
Commencez par l'approche naïve (qui ne fonctionne pas) pour une fonction qui appelle une méthode asynchrone (dans ce cas
setTimeout
) et renvoie un message:undefined
obtient enregistré dans ce cas, cargetMessage
retourne avant l'setTimeout
appel du rappel et met à jourouterScopeVar
.Les deux principaux moyens de le résoudre sont d'utiliser des rappels et des promesses :
Rappels
Le changement ici est qu'il
getMessage
accepte uncallback
paramètre qui sera appelé pour renvoyer les résultats au code appelant une fois disponible.Promesses
Les promesses offrent une alternative plus flexible que les rappels car elles peuvent être naturellement combinées pour coordonner plusieurs opérations asynchrones. A Promises / A + implémentation standard est nativement fournie dans Node.js (0.12+) et de nombreux navigateurs actuels, mais il est également mis en œuvre dans les bibliothèques comme Bluebird et Q .
jQuery différés
jQuery fournit des fonctionnalités similaires aux promesses avec ses différés.
asynchrone / attendre
Si votre environnement JavaScript inclut la prise en charge de
async
etawait
(comme Node.js 7.6+), vous pouvez utiliser les promesses de manière synchrone dans lesasync
fonctions:la source
function getMessage(param1, param2, callback) {...}
.async/await
échantillon, mais je rencontre des problèmes. Au lieu d'instancier unnew Promise
, je passe un.Get()
appel et n'ai donc accès à aucuneresolve()
méthode. Ainsi, mongetMessage()
retourne la promesse et non le résultat. Pourriez-vous modifier un peu votre réponse pour afficher une syntaxe de travail pour cela?.Get()
appel. Il est probablement préférable de poster une nouvelle question.Pour dire l'évidence, la coupe représente
outerScopeVar
.Les fonctions asynchrones ressemblent à ...
la source
Les autres réponses sont excellentes et je veux juste apporter une réponse simple à cela. Limiter aux appels asynchrones jQuery
Tous les appels ajax (y compris
$.get
ou$.post
ou$.ajax
) sont asynchrones.Considérant votre exemple
L'exécution du code commence à partir de la ligne 1, déclare la variable et déclenche et l'appel asynchrone à la ligne 2, (c'est-à-dire la post-demande) et continue son exécution à partir de la ligne 3, sans attendre que la post-requête termine son exécution.
Supposons que la demande de publication prenne 10 secondes, la valeur de
outerScopeVar
ne sera définie qu'après ces 10 secondes.Essayer,
Maintenant, lorsque vous exécutez cela, vous obtiendrez une alerte sur la ligne 3. Maintenant, attendez un certain temps jusqu'à ce que vous soyez sûr que la demande de publication a renvoyé une valeur. Ensuite, lorsque vous cliquez sur OK, dans la zone d'alerte, l'alerte suivante imprime la valeur attendue, car vous l'avez attendue.
Dans un scénario réel, le code devient,
Tout le code qui dépend des appels asynchrones, est déplacé à l'intérieur du bloc asynchrone, ou en attendant les appels asynchrones.
la source
or by waiting on the asynchronous calls
Comment fait-on cela?Dans tous ces scénarios,
outerScopeVar
une valeur est modifiée ou affectée de manière asynchrone ou se produit plus tard (en attendant ou en écoutant qu'un événement se produise), pour lequel l'exécution en cours n'attendra pas . Dans tous ces cas, le flux d'exécution en cours se traduit parouterScopeVar = undefined
Discutons de chaque exemple (j'ai marqué la partie qui est appelée de manière asynchrone ou retardée pour que certains événements se produisent):
1.
Ici, nous enregistrons un listeur d'événements qui sera exécuté lors de cet événement particulier. Ici le chargement de l'image. Ensuite, l'exécution en cours continue avec les lignes suivantes
img.src = 'lolcat.png';
et enalert(outerScopeVar);
attendant l'événement peut ne pas se produire. c'est-à-dire, funtionimg.onload
attendre que l'image référencée se charge, de manière asynchrone. Cela se produira tout l'exemple suivant - l'événement peut différer.2.
Ici, l'événement d'expiration joue le rôle, qui invoquera le gestionnaire après l'heure spécifiée. Le voici
0
, mais il enregistre toujours un événement asynchrone il sera ajouté à la dernière position de l'Event Queue
exécution, ce qui rend le délai garanti.3.
Cette fois, rappel ajax.
4.
Le nœud peut être considéré comme un roi du codage asynchrone. Ici, la fonction marquée est enregistrée comme un gestionnaire de rappel qui sera exécuté après la lecture du fichier spécifié.
5.
La promesse évidente (quelque chose sera fait à l'avenir) est asynchrone. voir Quelles sont les différences entre différé, promis et futur en JavaScript?
https://www.quora.com/Whats-the-difference-between-a-promise-and-a-callback-in-Javascript
la source