1

Есть массив items, с которым я работаю в цикле. В конце я вывожу console.log(items[a] + ' : ' + min); min выводится нормально, items[a] выводится как undefined. Как решить проблему с выводом items[a]?

var items = [1, 2, 3];
for (var a = 0; a < items.length; a++) {
setTimeout(function() {
    console.log(items[a]);
}, 0);
}

1 Answers1

3

На самом деле, единственная ваша проблема заключается в непонимании принципов работы асинхронного кода.

Для того, чтобы упростить объяснение, позволю себе использовать вместо вашей функции request вот такую асинхронную функцию:

var request = function(callback) {
    setTimeout(callback, 0);
}

Тогда, отбросив весь тот код, что не относится к сути вопроса, я получаю вот такой тестовый сценарий:

var request = function (callback) {
    setTimeout(callback, 0);
}

var items = [1, 2];
for (var i = 0; i < items.length; i++) {
    request(function() {
        console.log(items[i]);
    });
}

Как вы можете догадаться, этот код три раза выведет undefined в консоль.

В чем причина?

Причина кроется в асинхронной природе функции request, которая выполняет callback на следующем витке event loop. По-факту, этот код будет выполняться вот в такой последовательности:

  1. Инициализация i значением 0.
  2. Вызов request, которая откладывает выполнение функции-аргумента.
  3. Увеличение счетчика цикла (i = 1)
  4. Вызов request, которая откладывает выполнение функции-аргумента.
  5. Увеличение счетчика цикла (i = 2) и выход из цикла (2 = items.length).
  6. Выполнение функции из п.2
  7. Выполнение функции из п.4

А теперь самый интересный факт: на момент реального выполнения функции, переданной в request, поток выполнения программы уже дошел до конца цикла и переменная i имеет значение 2! Вполне очевидно, что items[2] == undefied.

Что с этим делать?

Традиционным методом решения этой проблемы является создание IIFE вокруг функции request с передачей переменной-счетчика:

var items = [1, 2];
for (var i = 0; i < items.length; i++) {
    (function (counter) {
        request(function() {
            console.log(items[counter]);
        });
    })(i);
}

При этом, текущее значение переменной-счетчика сохраняется в локальной области видимости, образованной замыканием (через аргумент counter) и уже не зависит изменения переменной i.

А вот и пример на JSFiddle.

Более современным способом решения, является использования let вместо var:

var items = [1, 2];
for (let i = 0; i < items.length; i++) {
    request(function() {
        console.log(items[i]);
    });
}

Использование let в данном случае позволяет привязать значение переменной к локальной области видимости цикла for.

А вот и пример на JSFiddle.

Dmitriy Simushev
  • 17,999
  • 5
  • 49
  • 85