Pourquoi mes variables restent-elles inchangées après avoir été modifiées au sein d'une fonction ? - Référence de code asynchrone
P粉978742405
P粉978742405 2023-10-13 19:47:02
0
2
549

Compte tenu des exemples suivants, pourquoiouterScopeVarn'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);

Pourquoiundefinedest-il affiché dans tous ces exemples ? Je n'ai pas besoin de solution, je veux savoirpourquoicela 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粉978742405
P粉978742405

répondre à tous (2)
P粉043432210

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'attendrela fin de ces opérations lentes. Mais en attendant, JavaScript continuera à exécuter d’autres codes. Le fait que JavaScript exécuteun autre codeen attendant la fin de l'opération lente rend le comportementasynchrone. Si JavaScript attend la fin d'une opération avant d'exécuter tout autre code, ce serait un comportementsynchrone.

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 chargerlolcat.png, ce qui est une opérationlolcat.png,这是一个sloooow操作。一旦这个缓慢的操作完成,回调函数就会被执行,但与此同时,JavaScript 将继续处理下一行代码;即alert(outerScopeVar)sloooow

. 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-à-dire alert(outerScopeVar).

未定义;因为alert()C'est pourquoi nous voyons l'alerte montrant

être traitée immédiatement, et non après le chargement de l'image.

alert(outerScopeVar)代码移到回调函数中。因此,我们不再需要将outerScopeVarPour corriger notre code, il suffit de déclarer la variable

comme variable globale.

var img = document.createElement('img'); img.onload = function() { var localScopeVar = this.width; alert(localScopeVar); }; img.src = 'lolcat.png';
Vous verreztoujoursles 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 corrigertous les

exemples, il suffit de déplacer le code qui a besoin de la réponse à l'action !

* Techniquement, vous pouvez également utilisereval(),但是eval()le mal à cette fin


Comment mettre en attente les appelants ?

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 appelantgetWidthOfImage()à 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 des variables globales (dans ce caswidth).

    P粉262113569

    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éponses aux questions actuelles

    Suivons d’abord les comportements courants. Dans tous les exemples,outerScopeVarest 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 unrappel.

    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.onloadPeut être appelédans le futur(si) l'image a été chargée avec succès.
    • setTimeoutpeut être appelésetTimeout可能会在延迟到期且超时尚未被clearTimeout取消后在将来的某个时间被调用。注意:即使使用0dans le futur
    • après l'expiration du délai et si le délai d'attente n'a pas été annulé par clearTimeout. Remarque : Même en utilisant 0comme 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).
    • Quand (et si) la requête Ajax s'est terminée avec succès,dans le futur$.postle rappel de jQuery
    • pourra être appelé.
    • fs.readFileNode.js'peut être appelédans le futur
    • lorsque le fichier a été lu avec succès ou qu'une erreur est générée.

    Dans 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 unflux 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écuterapour 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 uneboucle d'événement

    .

    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éplaceralertconsole.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 :

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

    Remarque :J'utilisesetTimeoutavec 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 laidouterScopeVar, 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); }
      Derniers téléchargements
      Plus>
      effets Web
      Code source du site Web
      Matériel du site Web
      Modèle frontal
      À propos de nous Clause de non-responsabilité Sitemap
      Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!