← Назад к вопросам

Асинхронная итерация по массиву с задержкой

2.3 Middle🔥 141 комментариев
#Node.js и JavaScript#Алгоритмы и структуры данных

Условие

Напишите функцию, которая проходит по массиву чисел и выводит индекс каждого элемента с задержкой 1 секунда между выводами.

const arr = [10, 20, 30, 40, 50];

// Ваша функция должна вывести:
// 0 (через 1 сек)
// 1 (через 2 сек)
// 2 (через 3 сек)
// и т.д.

Подвох

Популярная ошибка - использование var в цикле for с setTimeout, что приводит к выводу последнего индекса для всех итераций из-за особенностей замыканий.

Что проверяется

  • Понимание замыканий
  • Разница между var/let в циклах
  • Работа с асинхронным кодом

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение

Вариант 1: Использование let (Рекомендуется)

const arr = [10, 20, 30, 40, 50];

for (let i = 0; i < arr.length; i++) {
  setTimeout(() => {
    console.log(i);
  }, i * 1000);
}

// Вывод:
// 0 (через 1 сек)
// 1 (через 2 сек)
// 2 (через 3 сек)
// 3 (через 4 сек)
// 4 (через 5 сек)

Почему это работает:

  • let создаёт новое замыкание для каждой итерации
  • Каждый setTimeout получает свою копию переменной i
  • Поэтому каждый вывод показывает правильное значение

Вариант 2: Использование IIFE (Старый подход с var)

const arr = [10, 20, 30, 40, 50];

for (var i = 0; i < arr.length; i++) {
  (function(index) {
    setTimeout(() => {
      console.log(index);
    }, index * 1000);
  })(i);
}

Как это работает:

  • Сразу вызываем IIFE (Immediately Invoked Function Expression)
  • Передаём текущее значение i как аргумент index
  • Каждая IIFE создаёт своё замыкание с правильным значением
  • setTimeout получает правильное index из замыкания

Вариант 3: Использование async/await (Современный подход)

const arr = [10, 20, 30, 40, 50];

async function iterateWithDelay() {
  for (let i = 0; i < arr.length; i++) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log(i);
  }
}

iterateWithDelay();

// Вывод каждую секунду:
// 0
// 1
// 2
// 3
// 4

Преимущества:

  • Более читаемо и понятно
  • await паузирует выполнение
  • Нет проблем с замыканиями

Вариант 4: Использование forEach с замыканием

const arr = [10, 20, 30, 40, 50];

arr.forEach((value, i) => {
  setTimeout(() => {
    console.log(i);
  }, i * 1000);
});

Почему это работает:

  • forEach передаёт индекс как параметр
  • Каждая итерация создаёт своё замыкание
  • Проблемы с var не возникает

Вариант 5: Использование Array.from

const arr = [10, 20, 30, 40, 50];

Array.from({ length: arr.length }).forEach((_, i) => {
  setTimeout(() => {
    console.log(i);
  }, i * 1000);
});

Демонстрация проблемы с var

НЕПРАВИЛЬНО — var в цикле:

const arr = [10, 20, 30, 40, 50];

for (var i = 0; i < arr.length; i++) {
  setTimeout(() => {
    console.log(i);
  }, i * 1000);
}

// НЕПРАВИЛЬНЫЙ вывод:
// 5 (через 1 сек)
// 5 (через 2 сек)
// 5 (через 3 сек)
// 5 (через 4 сек)
// 5 (через 5 сек)

Почему выводит 5?

Шаг 1: var i = 0 (глобальная переменная)
Шаг 2: Цикл выполняется:
  i = 0 → setTimeout(..., 0) → ссылка на i
  i = 1 → setTimeout(..., 1000) → ссылка на i
  i = 2 → setTimeout(..., 2000) → ссылка на i
  i = 3 → setTimeout(..., 3000) → ссылка на i
  i = 4 → setTimeout(..., 4000) → ссылка на i
  i = 5 → цикл заканчивается

Шаг 3: Через 1 сек → console.log(i) → текущее значение i = 5
Шаг 4: Все setTimeout используют ОДНУ переменную i со значением 5

Ключевое различие:

var:  одна переменная для всего цикла
let:  новая переменная для каждой итерации

Объяснение замыканий

Замыкание (Closure) — это функция, которая имеет доступ к переменным из другой функции.

С var (ПЛОХО):

for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // Все callbacks ссылаются на ОДНУ i
  }, 1000);
}
// i = 3 → все выведут 3

С let (ХОРОШО):

for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // Каждый callback ссылается на СВОЮ i
  }, 1000);
}
// Выведет 0, 1, 2

Полный пример с использованием async/await

const arr = [10, 20, 30, 40, 50];

async function processArray() {
  for (let i = 0; i < arr.length; i++) {
    console.log(i);
    // Ждём 1 секунду перед следующей итерацией
    await new Promise(resolve => setTimeout(resolve, 1000));
  }
}

processArray().then(() => {
  console.log('Готово!');
});

Более сложный пример: обработка массива значений

const arr = [10, 20, 30, 40, 50];

async function processWithDelay() {
  for (let i = 0; i < arr.length; i++) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log(`Index: ${i}, Value: ${arr[i]}`);
  }
}

processWithDelay();

// Вывод каждую секунду:
// Index: 0, Value: 10
// Index: 1, Value: 20
// Index: 2, Value: 30
// Index: 3, Value: 40
// Index: 4, Value: 50

Тестирование

import { describe, it, expect, vi, beforeEach } from 'vitest';

describe('Асинхронная итерация', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  it('должен выводить правильные индексы с let', () => {
    const logs: number[] = [];
    const arr = [10, 20, 30, 40, 50];

    for (let i = 0; i < arr.length; i++) {
      setTimeout(() => {
        logs.push(i);
      }, i * 1000);
    }

    vi.advanceTimersByTime(4000);
    expect(logs).toEqual([0, 1, 2, 3]);
  });

  it('должен работать с async/await', async () => {
    const logs: number[] = [];
    const arr = [10, 20, 30, 40, 50];

    const processArray = async () => {
      for (let i = 0; i < arr.length; i++) {
        await new Promise(resolve => setTimeout(resolve, 1000));
        logs.push(i);
      }
    };

    processArray();
    await vi.runAllTimersAsync();
    expect(logs).toEqual([0, 1, 2, 3, 4]);
  });
});

Сравнение подходов

СпособПростотаЧитаемостьСовременность
let в циклеПростойПонятноСовременно
IIFE с varСредняяСложноватоУстаревший
async/awaitПростойОчень понятноОчень современно
forEachПростойХорошоСовременно

Вывод

Используйте let в цикле for — это самый простой и современный подход:

for (let i = 0; i < arr.length; i++) {
  setTimeout(() => {
    console.log(i);
  }, i * 1000);
}

Если нужна более читаемая асинхронная операция, используйте async/await:

async function iterate() {
  for (let i = 0; i < arr.length; i++) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log(i);
  }
}
Асинхронная итерация по массиву с задержкой | PrepBro