Les variables resteront-elles inchangées après modification au sein de la fonction ? - Référence de code asynchrone
P粉403804844
P粉403804844 2023-08-28 11:46:28
0
2
446
<p>鉴于以下示例,为什么 <code>outerScopeVar</code> 在所有情况下均未定义?</p> <pre class="brush:php;toolbar:false;">var externalScopeVar; var img = document.createElement('img'); img.onload = fonction() { externalScopeVar = this.width; } ; img.src = 'lolcat.png'; alert(outerScopeVar);</pre> <pre class="brush:php;toolbar:false;">var externalScopeVar; setTimeout(fonction() { externalScopeVar = 'Bonjour le monde asynchrone !'; }, 0); alert(outerScopeVar);</pre> <pre class="brush:php;toolbar:false;">// Exemple utilisant du jQuery var externalScopeVar; $.post('loldog', fonction(réponse) { externalScopeVar = réponse ; }); alert(outerScopeVar);</pre> <pre class="brush:php;toolbar:false;">// Exemple Node.js var externalScopeVar; fs.readFile('./catdog.html', function(erreur, données) { externalScopeVar = données ; }); console.log(outerScopeVar);</pre> <pre class="brush:php;toolbar:false;">// avec des promesses var externalScopeVar; maPromesse.then(fonction (réponse) { externalScopeVar = réponse ; }); console.log(outerScopeVar);</pre> <pre class="brush:php;toolbar:false;">// avec observables var externalScopeVar; monObservable.subscribe(fonction (valeur) { externalScopeVar = valeur ; }); console.log(outerScopeVar);</pre> <pre class="brush:php;toolbar:false;">// API de géolocalisation var externalScopeVar; navigator.geolocation.getCurrentPosition(fonction (pos) { externalScopeVar = pos; }); console.log(outerScopeVar);</pre> <p>为什么在所有这些示例中都输出 <code>undefined</code> ?我不需要解决方法,我想知道<strong>为什么</strong>发生这种情况。</p> <heure /> <blockquote> <p><strong>注意:</strong>以认同的更多简化示例。</p> </blockquote><p><br /></p>
P粉403804844
P粉403804844

répondre à tous(2)
P粉064448449

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.

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

. 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 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

exemples, tout ce que nous avons à faire est de déplacer le code qui a besoin de la réponse à l'action ! 🎜

* Techniquement, vous pouvez également utiliser eval(),但是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 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粉191610580

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, 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.onloadPeut être appelé dans le futur (si) l'image a été chargée avec succès.
  • setTimeout peut ê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 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).
  • Quand (et si) la requête Ajax s'est terminée avec succès, dans le futur $.post le 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 « quelque part 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-à-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 alertconsole.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

Souvent, vous devez 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);
}
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal