Отличие iterate от ручного обхода через keys и getItem

Библиотека localForage предоставляет унифицированный API поверх различных механизмов хранения (IndexedDB, WebSQL, localStorage), при этом сохраняя асинхронную модель работы. Одной из ключевых задач при работе с хранилищем становится обход всех сохранённых записей. Для этого используются два принципиальных подхода: метод iterate и ручной обход через keys с последующим getItem.

Эти подходы решают схожую задачу, но имеют разную архитектурную модель, производительность и поведенческие особенности.


Метод iterate как встроенный механизм обхода

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

Базовая сигнатура

localforage.iterate((value, key, iterationNumber) => {
  // обработка элемента
});

Метод принимает callback, который вызывается для каждой записи:

  • value — значение, сохранённое по ключу
  • key — строковый ключ записи
  • iterationNumber — порядковый номер итерации

Особенности работы iterate

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

Поведение можно охарактеризовать следующими свойствами:

  • последовательный потоковый обход данных
  • минимизация количества промежуточных операций
  • отсутствие необходимости хранить массив ключей в памяти
  • оптимизация под IndexedDB (при использовании соответствующего драйвера)

Ручной обход через keys и getItem

Альтернативный подход заключается в комбинации двух методов:

const keys = await localforage.keys();

for (const key of keys) {
  const value = await localforage.getItem(key);
}

Этот метод состоит из двух этапов:

1. Получение списка ключей

localforage.keys()

Возвращает массив всех ключей, хранящихся в базе.

2. Последовательное или параллельное извлечение значений

localforage.getItem(key)

Каждый ключ требует отдельного асинхронного запроса.


Архитектурное различие подходов

Модель выполнения iterate

iterate работает как единый поток обхода, где:

  • данные читаются напрямую из хранилища
  • отсутствует промежуточное представление всех ключей
  • итерация контролируется внутренним курсором (особенно в IndexedDB)

Фактически это ближе к cursor-based iteration в IndexedDB.


Модель keys + getItem

Ручной подход строится вокруг двухфазной модели:

  1. Получение полного списка ключей
  2. Последовательные обращения к каждому элементу

Это создаёт дополнительный слой абстракции:

  • сначала извлекается метаданные (ключи)
  • затем выполняются N независимых запросов данных

Сравнение производительности

Количество операций

iterate:

  • 1 проход по хранилищу
  • минимальное число обращений к драйверу

keys + getItem:

  • 1 запрос на keys
  • N запросов на getItem

При большом объёме данных разница становится существенной.


Использование памяти

iterate:

  • не требует хранения списка ключей
  • постоянное потребление памяти

keys + getItem:

  • требует хранения массива ключей
  • при больших данных возможны пиковые нагрузки на память

Поведение в IndexedDB

В IndexedDB реализация iterate часто использует курсоры (IDBCursor), что обеспечивает:

  • ленивую загрузку данных
  • отсутствие необходимости материализовать весь набор ключей

В ручном подходе каждый getItem может запускать отдельную транзакцию или запрос, что увеличивает накладные расходы.


Контроль потока выполнения

iterate

Контроль ограничен callback-функцией:

localforage.iterate((value, key) => {
  if (key === 'stop') return; // не прерывает обход
});

Особенность: стандартный return не останавливает итерацию. Для прекращения обхода требуется использование исключений:

localforage.iterate((value, key) => {
  if (key === 'stop') throw new Error('break');
});

Это делает управление потоком менее интуитивным, но обеспечивает непрерывность курсорной обработки.


keys + getItem

Ручной подход позволяет полностью управлять процессом:

for (const key of keys) {
  if (key === 'stop') break;
  const value = await localforage.getItem(key);
}

Здесь доступны:

  • break
  • continue
  • параллельная обработка через Promise.all

Параллелизм и асинхронные стратегии

iterate

Обработка строго последовательная. Это обусловлено тем, что метод:

  • ориентирован на потоковую обработку
  • может зависеть от состояния курсора

Параллелизм внутри iterate отсутствует.


keys + getItem

Позволяет строить разные модели:

Последовательная обработка

for (const key of keys) {
  await localforage.getItem(key);
}

Параллельная обработка

const values = await Promise.all(
  keys.map(k => localforage.getItem(k))
);

Возможность параллелизма делает этот подход гибким, но потенциально более тяжёлым для памяти и драйвера.


Поведение при изменении данных во время обхода

iterate

Так как используется курсорная модель:

  • изменения в хранилище могут влиять на результат обхода
  • поведение зависит от реализации драйвера
  • возможна «плавающая» консистентность

keys + getItem

Так как список ключей фиксируется в момент вызова keys():

  • обход работает по снимку состояния
  • последующие изменения не влияют на уже полученный массив ключей

Это делает поведение более предсказуемым.


Типичные сценарии использования

iterate предпочтителен при:

  • обработке больших объёмов данных
  • потоковой трансформации значений
  • необходимости минимального потребления памяти
  • работе с IndexedDB как основным драйвером

keys + getItem предпочтителен при:

  • необходимости полного контроля логики обхода
  • параллельной обработке данных
  • создании снимка состояния хранилища
  • фильтрации и сложных вычислениях на уровне приложения

Семантические различия подходов

iterate

  • абстракция уровня хранилища
  • логика встроена в API
  • минимальный контроль над процессом

keys + getItem

  • абстракция уровня приложения
  • логика полностью определяется разработчиком
  • максимальная гибкость ценой дополнительных операций

Влияние выбора подхода на архитектуру

Использование iterate приводит к архитектуре, где хранилище рассматривается как поток данных. Это сближает модель с обработкой стримов.

Использование keys + getItem формирует модель, где хранилище выступает как индексируемая база данных с возможностью выборочного доступа и последующей агрегации.

Разница проявляется особенно сильно при масштабировании:

  • потоковая модель устойчивее к росту объёма данных
  • индексная модель проще для сложной бизнес-логики

Поведенческие ограничения

iterate

  • невозможно получить массив всех значений сразу без дополнительной агрегации
  • ограниченные возможности остановки
  • отсутствие параллелизма

keys + getItem

  • дополнительные накладные расходы на запрос ключей
  • возможные гонки при изменении данных
  • необходимость ручного управления ошибками и порядком выполнения