web-dev-qa-db-fra.com

Comment éviter l'accès variable mutable de la fermeture

J'ai un code comme celui-ci:

for(var id=0; id < message.receiver.length; id++){
   var tmp_id = id;
   zlib.gzip(JSON.stringify(message.json), function(err, buffer){
                        ...
   pushStatusPool[message.receiver[tmp_id]] = null; // fix memory leak
   delete pushStatusPool[message.receiver[tmp_id]];
   ...
   });
}

Et j’ai été averti que l’utilisation de tmp_id en fermeture pouvait poser problème car il s’agissait d’une variable mutable.

Comment pourrais-je éviter cela? Je veux dire, comment pourrais-je envoyer une variable immuable à callback puisqu'il s'agit d'une boucle for et que je ne peux pas changer le code de zlib.gzip? Ou en d'autres termes, comment pourrais-je passer un argument à une fermeture?

31
bxshi

Vous devez créer une étendue pour capturer correctement tmp_id à l'aide d'une fonction à exécution automatique. En effet, la totalité de la boucle for correspond à une seule portée, ce qui signifie qu'à chaque fois, vous capturez la même variable. Ainsi, le callback se retrouvera avec les mauvais identifiants, car la valeur de temp_id sera modifiée avant l'appel du callback.

J'ignorerais (ou éteindrais) l'avertissement, ce qui semble se plaindre du fait que, puisque temp_id est mutable, vous pourriez le réaffecter. C'est un peu bête. Si vous voulez vraiment résoudre ce problème, essayez d’utiliser le mot clé const au lieu de var.

for(var id=0; id < message.receiver.length; id++){
   (function(){
       const tmp_id = id;
       zlib.gzip(JSON.stringify(message.json), function(err, buffer){
                        ...
           pushStatusPool[message.receiver[tmp_id]] = null; // fix memory leak
           delete pushStatusPool[message.receiver[tmp_id]];
           ...
       });
   })();
}
41
user24359

J'ai rencontré le même problème et l'ai résolu en modifiant légèrement la réponse de user24359, en passant l'identifiant à la fermeture:

for(var id=0; id < message.receiver.length; id++){
   (function(tmp_id){
       zlib.gzip(JSON.stringify(message.json), function(err, buffer){
                        ...
           pushStatusPool[message.receiver[tmp_id]] = null; // fix memory leak
           delete pushStatusPool[message.receiver[tmp_id]];
           ...
       });
   })(id);
}
10
gennadi.w

voici une simplification de l'excellente réponse de user24359. Voici la solution:

var object = {a:1,b:2};

for (var y in object){
    (function(){const yyy = y;
        setTimeout(function(){console.log(yyy)},3000);})();
}

Le code ci-dessus enregistre a b et est la solution. Le code suivant enregistre b b:

var object = {a:1,b:2};
for (var y in object){

    setTimeout(function(){console.log(y)},3000);
}
2
Michael Moeller

@ user24359 answer est une bonne solution mais vous pouvez simplement remplacer le mot clé var par le mot clé let.

for(var id=0;

devient

for(let id=0;

Voir détails ici .

Edit : Comme Heriberto Juárez l’a suggéré, cela ne fonctionnera que pour les navigateurs prenant en charge EcmaScript6.

0
Bludwarf

J'ai rencontré le même problème de rapporteur. Résolu en utilisant le code suivant -

(function(no_of_agents){
              ptor.element.all(by.repeater('agent in agents').column('displayName')).then(function(firstColumn){
                    console.log(i, '>>>>>Verifying the agent Name');
                    var agentsSorted = sortAgentsByName();
                    //verify the agent name
                    expect(firstColumn[no_of_agents].getText()).toEqual(agentsSorted[no_of_agents].name);
                    //now click on the agent name link
                    firstColumn[no_of_agents].click();
                    ptor.sleep(5000);
              });
            })(no_of_agents);
0
Piyush Jajoo

La création de fermetures dans une boucle avec var (tmp_id) se trouvant dans la partie supérieure de la fonction de rappel est une erreur commune qui devrait être évitée car var n'est pas couvert par des blocs. Pour cette raison, et parce que chaque fermeture, créée dans la boucle, partage le même environnement lexical , la variable sera toujours la dernière valeur itérée (c'est-à-dire message.receiver.length - 1 en tant que tmp_id) lorsque la fonction de rappel sera invoquée. Votre IDE détecte ce comportement et se plaint correctement.

Pour éviter cet avertissement, il existe plusieurs solutions:

  • Remplacez var parleten veillant à ce que chaque fermeture créée ait son propre tmp_id périmé défini à chaque itération:

    for (var id = 0; id < message.receiver.length; id++) {
      let tmp_id = id;
      zlib.gzip(JSON.stringify(message.json), function(err, buffer) {
        // Do something with tmp_id ...
      });
    }
    
  • Créez un environnement lexical dans chaque itération en utilisant IIFE like gennadi.w did .

  • Créez une fonction de rappel dans chaque itération à l'aide d'une fonction d'usine (createCallback):

    const createCallback = tmp_id => function(err, buffer) {
      // Do something with tmp_id ...
    };
    for (var id = 0; id < message.receiver.length; id++) {
      zlib.gzip(JSON.stringify(message.json), createCallback(id));
    }
    
  • bind la (les) variable (s) de la fonction de rappel dans laquelle elles obtiennent préfixé à ses paramètres:

    for (var id = 0; id < message.receiver.length; id++) {
      zlib.gzip(JSON.stringify(message.json), function(tmp_id, err, buffer) {
        // Do something with tmp_id (passed as id) ...
      }.bind(this, id));
    }
    

Si possible, var devrait être évité à compter de ECMAScript 2015 en raison de ces comportements sujets aux erreurs.

0
Boghyon Hoffmann