Pourquoi mes variables restent inchangées après modification au sein d'une fonction - Comprendre le code asynchrone
P粉212114661
P粉212114661 2023-10-13 21:51:11
0
2
629

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粉212114661
P粉212114661

répondre à tous(2)
P粉930534280

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, il suffit de déplacer le code qui nécessite 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粉178894235

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 « 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-à-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

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