Как избежать доступа к изменяемой переменной из закрытия

У меня есть такой код:

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]];
   ...
   });
}

И я получил предупреждение о том, что использование tmp_id в закрытии может вызвать проблему, потому что это изменяемая переменная.

Как я мог избежать этого? Я имею в виду, как я могу отправить неизменяемую переменную для обратного вызова, поскольку это цикл for, и я не могу изменить код zlib.gzip? Или, другими словами, как я могу передать аргумент закрытию?

Ответ 1

Вам нужно создать область для правильного захвата tmp_id с помощью функции самоисполнения. Это потому, что весь цикл for является одной областью, означающей каждый раз, вы захватываете одну и ту же переменную. Таким образом, обратный вызов будет иметь неправильные идентификаторы, потому что значение temp_id будет изменено до вызова обратного вызова.

Я бы проигнорировал (или отключил) предупреждение, хотя, похоже, жалуется, что, поскольку temp_id изменен, вы можете переназначить его. Это глупо. Если вы действительно хотите это исправить, попробуйте использовать ключевое слово const вместо 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]];
           ...
       });
   })();
}

Ответ 2

Я столкнулся с той же проблемой и решил немного изменить ответ user24359, передав id закрытию:

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);
}

Ответ 3

здесь упрощение user24359 отличного ответа. Это решение:

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

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

Вышеприведенный код регистрирует a и является решением. Следующие журналы кода b b:

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

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

Ответ 4

Я столкнулся с той же проблемой в транспортире. Решил его, используя следующий код -

(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);

Ответ 5

@user24359 ответ - хорошее решение, но вы можете просто заменить ключевое слово var ключевым словом let.

for(var id=0;

становится

for(let id=0;

Подробнее здесь.

Ответ 6

for (var id = 0; id < message.receiver.length; id++) {
  var tmp_id = id;
  zlib.gzip(JSON.stringify(message.json), (err, buffer) => {
    // Something with tmp_id ...
  });
}

Создание замыканий в цикле с var (tmp_id), находящимся в верхней части функции обратного вызова, является распространенной ошибкой что следует избегать из-за того, что var не является блочным. Из-за этого и потому, что каждое замыкание, созданное в цикле, имеет те же лексическую среду, переменная всегда будет последним итерированным значением (т.е. message.receiver.length-1 как tmp_id) при вызове функции обратного вызова. Ваша IDE обнаруживает это поведение и жалуется правильно.

Чтобы избежать предупреждения, существует несколько решений:

  • Замените var на let ( var let tmp_id = id), гарантируя каждому созданному закрытию свой собственный охваченный tmp_id определенный на каждой итерации:

    for (var id = 0; id < message.receiver.length; id++) {
      let tmp_id = id;
      zlib.gzip(JSON.stringify(message.json), (err, buffer) => {
        // Do something with tmp_id ...
      });
    }
    
  • Создайте лексическую среду на каждой итерации, используя IIEF, например gennadi.w сделал.

  • Создайте функцию обратного вызова в каждой итерации с помощью функции factory (createCallback):

    const createCallback = tmp_id => (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 переменная в функции обратного вызова, в которую они добавляются к ее параметрам:

    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));
    }
    

    Примечание: Это возможно только на function s. Функции стрелок (=>) не поддерживают Bind.


Как правило, следует избегать var (как из ECMAScript 2015) из-за такого поведения, подверженного ошибкам.