Maison > Questions et réponses > le corps du texte
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.
P粉9305342802023-10-14 16:59:42
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.
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é.
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.
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);
Dans le code ci-dessus, nous demandons à JavaScript de charger lolcat.png
, ce qui est une opération lolcat.png
,这是一个sloooow 操作。一旦这个缓慢的操作完成,回调函数就会被执行,但与此同时,JavaScript 将继续处理下一行代码;即alert(outerScopeVar)
sloooow
alert(outerScopeVar)
.
未定义
;因为 alert()
C'est pourquoi nous voyons l'alerte montrant
alert(outerScopeVar)
代码移到回调函数中。因此,我们不再需要将 outerScopeVar
Pour corriger notre code, il suffit de déclarer la variable
var img = document.createElement('img'); img.onload = function() { var localScopeVar = this.width; alert(localScopeVar); }; img.src = 'lolcat.png';Vous verrez toujours les rappels spécifiés en tant que fonctions car c'est le seul* moyen en JavaScript de définir du code, puis de l'exécuter plus tard.
function() { /* Do Something */ }
Donc, dans tous nos exemples, est le rappel ; pour corriger tous les
* Techniquement, vous pouvez également utiliser eval()
,但是eval()
le mal à cette fin
Vous avez peut-être actuellement un code similaire à celui-ci ;
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);
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
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); });
...Comme précédemment, notez que nous avons pu supprimer la variable globale (dans ce cas width
).
P粉1788942352023-10-14 10:02:51
Réponse en un mot : Asynchronicité.
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.
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 utilisant 0
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 jQuery fs.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. .C'est-à-dire que le code asynchrone mis en évidence dans la forme rouge dessinée à la main ne peut s'exécuter qu'une fois que tout le code synchrone restant dans son bloc de code respectif a été exécuté :
En bref, les fonctions de rappel sont créées de manière synchrone mais exécutées de manière asynchrone. Vous ne pouvez pas compter sur l'exécution d'une fonction asynchrone tant que vous ne savez pas qu'elle a été exécutée, comment procéder ? 🎜
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à.
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 :
var outerScopeVar; helloCatAsync(); alert(outerScopeVar); function helloCatAsync() { setTimeout(function() { outerScopeVar = 'Nya'; }, Math.random() * 2000); }
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) :
// 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 an 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 an argument callback('Nya'); }, Math.random() * 2000); }
Extrait de code pour l'exemple ci-dessus :
// 1. Call helloCatAsync passing a callback function,
// which will be called receiving the result from the async operation
console.log("1. function called...")
helloCatAsync(function(result) {
// 5. Received the result from the async function,
// now do whatever you want with it:
console.log("5. result is: ", result);
});
// 2. The "callback" parameter is a reference to the function which
// was passed as an argument from the helloCatAsync call
function helloCatAsync(callback) {
console.log("2. callback here is the function passed as argument above...")
// 3. Start async operation:
setTimeout(function() {
console.log("3. start async operation...")
console.log("4. finished async operation, calling the callback, passing the result...")
// 4. Finished async operation,
// call the callback passing the result as argument
callback('Nya');
}, Math.random() * 2000);
}