Compte tenu des exemples suivants, pourquoi outerScopeVar
n'est-il 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);
// with observables var outerScopeVar; myObservable.subscribe(function (value) { outerScopeVar = value; }); console.log(outerScopeVar);
// geolocation API var outerScopeVar; navigator.geolocation.getCurrentPosition(function (pos) { outerScopeVar = pos; }); console.log(outerScopeVar);
Pourquoi undefined
est-il affiché dans tous ces exemples ? Je n'ai pas besoin de solution, je veux savoir pourquoi cela se produit.
Remarque : Il s'agit d'une question spécifique sur l'asynchronicité JavaScript. N'hésitez pas à améliorer cette question et à ajouter des exemples plus simplifiés sur lesquels la communauté peut s'entendre.
La réponse de Fabrício est très correcte ; mais j'aimerais compléter sa réponse par quelque chose d'un peu moins technique, en me concentrant sur l'aide à expliquer le concept d'asynchronicité à travers des analogies.
Analogie...
Hier, j'avais besoin d'obtenir des informations auprès de mes collègues pour le travail que j'effectuais. Je l'ai appelé ; la conversation s'est déroulée comme ceci :
À ce stade, j'ai raccroché. Comme j'avais besoin des informations de Bob pour compléter mon rapport, j'ai quitté le rapport, je suis allé prendre une tasse de café, puis j'ai lu quelques e-mails. 40 minutes plus tard (Bob est lent), Bob a rappelé et m'a donné les informations dont j'avais besoin. À ce stade, je continue de travailler sur mon rapport puisque j’ai toutes les informations dont j’ai besoin.
Imaginez si la conversation se déroulait ainsi ;
Je me suis assis là et j'ai attendu. et j'ai attendu. et j'ai attendu. 40 minutes. Ne faites rien d'autre qu'attendre. Finalement, Bob m'a donné l'information, nous avons raccroché et j'ai terminé mon rapport. Mais j'ai 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. Le chargement d'images, le chargement de fichiers à partir du disque et la demande de pages via AJAX sont toutes des opérations lentes (dans le contexte de l'informatique moderne).
JavaScript vous permet d'enregistrer une fonction de rappel qui sera exécutée une fois les opérations lentes terminées, au lieu d'attendre la fin de ces opérations lentes. Mais en attendant, JavaScript continuera à 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 attend la fin d'une opération avant d'exécuter tout autre code, ce serait un comportement synchrone.
Dans le code ci-dessus, nous demandons à JavaScript de charger
. Une fois cette opération lente terminée, la fonction de rappel sera exécutée, mais en attendant, JavaScript continuera à traiter la ligne de code suivante ; c'est-à-direlolcat.png
, ce qui est une opérationlolcat.png
,这是一个sloooow 操作。一旦这个缓慢的操作完成,回调函数就会被执行,但与此同时,JavaScript 将继续处理下一行代码;即alert(outerScopeVar)
sloooowalert(outerScopeVar)
.未定义
;因为alert()
C'est pourquoi nous voyons l'alerte montrantalert(outerScopeVar)
代码移到回调函数中。因此,我们不再需要将outerScopeVar
Pour corriger notre code, il suffit de déclarer la variablefunction() { /* Do Something */ }
Donc, dans tous nos exemples, est le rappel ; pour corriger tous les* Techniquement, vous pouvez également utiliser
eval()
,但是eval()
le mal à cette finComment mettre en attente les appelants ?
Vous avez peut-être actuellement un code similaire à celui-ci ;
Mais nous le savons maintenant
返回outerScopeVar
会立即发生;在onload
回调函数更新变量之前。这会导致getWidthOfImage()
返回undefined
,并发出警报undefined
.Pour résoudre ce problème, nous devons autoriser la fonction appelant
;getWidthOfImage()
à enregistrer un rappel, puis à déplacer l'alerte de largeur à l'intérieur de ce rappel...Comme précédemment, notez que nous avons pu supprimer la variable globale (dans ce cas
width
).Réponse en un mot : Asynchronicité.
Avant-propos
Ce sujet a été répété au moins des milliers de fois sur Stack Overflow. Je tiens donc d’abord à souligner quelques ressources très utiles :
Réponse de @Felix Kling à "Comment renvoyer une réponse d'un appel asynchrone ?". Voir son excellente réponse expliquant les processus synchrones et asynchrones, ainsi que la section "Réorganiser votre code".
@Benjamin Gruenbaum a également déployé beaucoup d'efforts pour expliquer l'asynchronicité au sein du même fil.
La réponse de @Matt Esch à "Obtenir des données à partir de fs.readFile" explique également très bien l'asynchronicité d'une manière simple.
Réponses aux questions actuelles
Suivons d’abord les comportements courants. Dans tous les exemples,
outerScopeVar
est modifié à l'intérieur de la fonction . La fonction n'est évidemment pas exécutée immédiatement ; elle est affectée ou passée en paramètre. C'est ce que nous appelons un rappel.Maintenant, la question est : quand ce rappel est-il appelé ?
Cela dépend de la situation spécifique. Essayons à nouveau de suivre certains comportements courants :
img.onload
Peut être appelé dans le futur (si) l'image a été chargée avec succès.setTimeout
peut être appelésetTimeout
可能会在延迟到期且超时尚未被clearTimeout
取消后在将来的某个时间被调用。注意:即使使用0
dans le futurclearTimeout
. Remarque : Même en utilisant0
comme délai, tous les navigateurs ont une limite supérieure pour le délai d'expiration minimum (spécifié à 4 millisecondes dans la spécification HTML5).$.post
le rappel de jQueryfs.readFile
Node.js' peut être appelé dans le futurDans tous les cas, nous avons un rappel qui pourrait s'exécuter à un moment donné dans le futur. Ce « quelquefois dans le futur » est ce que nous appelons un flux asynchrone
.L'exécution asynchrone est exclue du processus synchrone. Autrement dit, pendant l'exécution de la pile de code synchrone, le code asynchrone s'exécutera pour toujours
. C'est pour cela que JavaScript est monothread.Plus précisément, lorsque le moteur JS est inactif - n'exécutant pas un tas de code (a)synchrone - il interrogera les événements susceptibles de déclencher un rappel asynchrone (par exemple, délai d'attente, réponse 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 vraiment simple. La logique qui repose sur l'exécution d'une fonction asynchrone doit être initiée/appelée depuis cette fonction asynchrone. Par exemple, déplacer
alert
和console.log
à l'intérieur de la fonction de rappel affichera le résultat attendu car le résultat est disponible à ce moment-là.Implémentez votre propre logique de rappel
Vous devez souvent effectuer plus d'opérations sur le résultat d'une fonction asynchrone, ou effectuer différentes opérations sur le résultat en fonction de l'endroit où la fonction asynchrone est appelée. Prenons un exemple plus complexe :
Remarque : J'utilise
setTimeout
avec un délai aléatoire comme fonction asynchrone générique ; le même exemple fonctionne pour Ajax, readFile, onload et tout autre flux asynchrone.Cet exemple a apparemment le même problème que les autres exemples ; il n'attend pas que la fonction asynchrone s'exécute.
Résolvons ce problème en implémentant notre propre système de rappel. D'abord, on se débarrasse de ce laid
outerScopeVar
, qui est complètement inutile dans ce cas. Ensuite, nous ajoutons un paramètre qui accepte un argument de fonction, notre rappel. Une fois l'opération asynchrone terminée, nous appelons ce rappel et transmettons le résultat. Mise en œuvre (veuillez lire les commentaires dans l'ordre) :Extrait de code pour l'exemple ci-dessus :