LINUX.ORG.RU

JavaScript, циклы и генераторы

 


0

1

Как в Джаваскрипте сделать вызов итератора по массиву через setInterval? То есть, сделать функцию, которую бы мог по таймеру дёргать setInterval, и которая при каждом вызове бы что-то делала с очередным элементом массива.

В современных языках программирования много средств для написания простого понятного выразительного кода. Сейчас попробовал на Джаваскрипте. А нихрена. Оказалось, далеко не все массивы умеют .forEach, forEach не сочетается с yield, setInterval не принимает генераторы и т. д. В итоге пришёл к синтаксису недо-Си — имитации цикла for через if с глобальными переменными:

    let pause = 1000;
    let length = 10;
    let i = 0;
    let doRoll = null;

    function getNext() {
        console.log(array1[i]);
        i++;
        if (i >= array1.length) {
            clearInterval(doRoll);
            doRoll = null;
        }
    }

    function startRolling() {
        if (doRoll) {
            clearInterval(doRoll);
            doRoll = null;
        } else {
            i = 0;
            doRoll = setInterval(getNext, pause);
        }
    }

Как это записать покрасивее?

★★★★★

Вот несколько вариантов, как сделать код чище и современнее — от небольших правок до более архитектурно выверенных решений. Вариант 1: Небольшие улучшения (минимальные правки)

Сохраняем логику, но делаем код читаемее:

js

const PAUSE = 1000;
const array = array1; // лучше не использовать магические имена вроде array1

let index = 0;
let rollInterval = null;

function getNext() {
  console.log(array[index]);
  index++;

  if (index >= array.length) {
    clearInterval(rollInterval);
    rollInterval = null;
  }
}

function startRolling() {
  if (rollInterval) {
    clearInterval(rollInterval);
  }

  index = 0;
  rollInterval = setInterval(getNext, PAUSE);
}

Что улучшено:

Константы вместо переменных для неизменяемых значений (PAUSE).
Понятные имена переменных (index, rollInterval вместо i, doRoll).
Убрана лишняя проверка doRoll = null после clearInterval — она избыточна.
Переименован array1 в array (или лучше дать осмысленное имя, например numbers, items и т. п.).

Вариант 2: Инкапсуляция состояния (более чистый подход)

Выносим состояние внутрь объекта, чтобы не засорять глобальную область видимости:

js

const PAUSE = 1000;

const roller = (array) => {
  let index = 0;
  let intervalId = null;

  const getNext = () => {
    console.log(array[index]);
    index++;

    if (index >= array.length) {
      clearInterval(intervalId);
      intervalId = null;
    }
  };

  const start = () => {
    if (intervalId) clearInterval(intervalId);
    index = 0;
    intervalId = setInterval(getNext, PAUSE);
  };

  return { start };
};

// Использование:
const myRoller = roller(array1);
myRoller.start();

Плюсы:

Состояние (index, intervalId) скрыто внутри замыкания.
Нет глобальных переменных.
Можно создавать несколько независимых «роллеров» для разных массивов.

Вариант 3: Использование setTimeout вместо setInterval (более предсказуемо)

Иногда вместо setInterval лучше использовать рекурсивный setTimeout — это проще контролировать и отлаживать:

js

const PAUSE = 1000;

const rollArray = (array, pause) => {
  let index = 0;
  let timeoutId = null;

  const next = () => {
    if (index >= array.length) return;

    console.log(array[index]);
    index++;

    timeoutId = setTimeout(next, pause);
  };

  const start = () => {
    if (timeoutId) clearTimeout(timeoutId);
    index = 0;
    next();
  };

  return { start };
};

// Использование:
const roller = rollArray(array1, PAUSE);
roller.start();

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

Проще управлять остановкой и перезапуском.
Не нужно проверять условие остановки внутри интервала — просто не планируем следующий шаг.
Меньше шансов «накопить» лишние таймеры при частых перезапусках.

Дополнительные советы

Имена переменных. Избегайте имён вроде array1, i, doRoll. Лучше: items, index, rollingInterval.
Магические значения. Выносите числа (1000, 10 и т. д.) в константы или параметры.
Обработка пустого массива. Если массив может быть пустым, стоит добавить проверку в начале start, чтобы не запускать таймер впустую.
Безопасность. Если массив может измениться во время прокрутки, подумайте, нужно ли фиксировать его длину или использовать копию.
Irma ★★★★
()

далеко не все массивы умеют .forEach

Не помню, но это какие-то объекты браузера типа HTMLCollection и чё-то такое. Чтобы по ним делать .forEach делают либо Array.from(collection).forEach либо [...collection].forEach

forEach не сочетается с yield

В каком смысле?

setInterval не принимает генераторы

куда он должен их принимать?

Bad_ptr ★★★★★
()

А глобальные переменные лучше прятать в замыкания. Чтобы у каждого цикла были свои переменные, а не общие для всех.

Bad_ptr ★★★★★
()
Ответ на: комментарий от Bad_ptr

А глобальные переменные лучше прятать в замыкания. Чтобы у каждого цикла были свои переменные, а не общие для всех.

Можно пример? Как этот подход применить к doRoll? Она используется как флаг, запускался ли уже скрипт.

question4 ★★★★★
() автор топика
Ответ на: комментарий от Bad_ptr

это какие-то объекты браузера типа HTMLCollection

Именно. Но с другими объектами прокатывает.

forEach не сочетается с yield

В каком смысле?

Пытался yield поставить внутри forEach.

setInterval не принимает генераторы

куда он должен их принимать?

Например, setInterval(generator1.next, 10000)

question4 ★★★★★
() автор топика

глобальными переменными:

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

yield

Это ужасный костыль, вообще не надо им пользоваться. На самом деле это только обёртка к чему-то. Если хочешь чтобы часть функции выполнилась после какого-то ожидания - выдели её в отдельную функцию и повесь на таймер, обработчик события или колбек. Выглядеть будет намного читабельнее чем псевдоасинхронные костыли.

.forEach

А как ты собрался forEach с таймером скрещивать?

firkax ★★★★★
()
  • Markdown
Пустая строка (два раза Enter) начинает новый абзац. Знак '>' в начале абзаца выделяет абзац курсивом цитирования.
Внимание: прочитайте описание разметки Markdown.
Используйте Ctrl-Enter для размещения комментария