JavaScript IndexedDB
Изучите IndexedDB в JavaScript: открытие базы, onupgradeneeded, объектные хранилища, индексы, транзакции, добавление и удаление записей.
IndexedDB — это мощный клиентский API для хранения данных, более функциональный, чем другие решения локального хранилища, доступные в браузерах. Он позволяет веб-приложениям асинхронно сохранять и обрабатывать значительные объёмы структурированных данных. IndexedDB идеально подходит для приложений, которым требуется офлайн-хранение данных, высокая производительность и богатые возможности запросов без зависимости от сетевого соединения.
В этой главе рассматривается всё необходимое для начала работы с IndexedDB: открытие базы данных и обработка обновлений версии, создание объектных хранилищ и индексов, выполнение транзакций, а также полный цикл CRUD (добавление, получение, обновление, удаление) включая итерацию с курсорами и запросы через индексы.
Когда использовать IndexedDB
IndexedDB хранит практически любые JavaScript-значения — object'ы, array'ы, даты, blob'ы и файлы — не только string'и, и может вмещать сотни мегабайт, значительно превышая лимит ~5 МБ localStorage и sessionStorage. Это транзакционное NoSQL-хранилище «ключ/значение» с индексами, что делает его правильным выбором для:
- Офлайн-first приложений, которым необходима работа без сетевого соединения.
- Кэширования больших наборов данных — ответов API, документов или медиафайлов.
- Запросов к записям по индексированным полям, без загрузки всего в память.
Если вам нужно сохранить лишь несколько небольших string-значений, воспользуйтесь более простыми API Web Storage. Обзор управления браузером квотами хранилища между этими механизмами см. в главе Storage API.
Событийный асинхронный API
Каждая операция IndexedDB является асинхронной и событийно-ориентированной. Вызовы, такие как indexedDB.open() или store.get(), немедленно возвращают объект запроса; фактический результат поступает позже через обработчики событий onsuccess / onerror. Код никогда не блокируется в ожидании дискового ввода-вывода. Именно поэтому нельзя прочитать результат синхронно сразу после отправки запроса — нужно дождаться события. (Современный код часто оборачивает эти запросы в Promise, однако нативный API сам по себе основан на обратных вызовах, что и демонстрируют приведённые ниже примеры.)
Настройка базы данных IndexedDB
Шаг 1: Открытие базы данных
Чтобы использовать IndexedDB, первым шагом является открытие базы данных с помощью indexedDB.open(name, version). Необязательный второй аргумент — целочисленный номер версии. Если база данных не существует или запрошенная версия выше сохранённой, возникает событие onupgradeneeded — это единственное место, где разрешено изменять структуру базы данных (создавать или удалять объектные хранилища и индексы).
Вызов возвращает запрос и может инициировать три события:
onsuccess— база данных открыта и готова к использованию (event.target.result— этоIDBDatabase).onerror— открытие завершилось ошибкой.onupgradeneeded— необходимо изменение схемы (см. Шаг 2).
Вот как можно создать или открыть базу данных IndexedDB. После успешной операции мы закрываем базу данных, чтобы избежать нежелательных побочных эффектов в других примерах. В собственном коде этот шаг можно пропустить или включить при необходимости.
Предупреждение: Вызов deleteDatabase() удалит все данные этой базы данных. Это предназначено только для тестирования и не должно использоваться в продакшене.
Шаг 2: Создание объектных хранилищ
После открытия базы данных можно создать объектное хранилище, аналогичное таблице в реляционных базах данных. Обратите внимание, что это возможно только во время обновления версии (при открытии базы данных с более высоким номером версии). Прослушиваемое событие — onupgradeneeded.
При создании хранилища выбирается способ определения первичного ключа каждой записи:
{ keyPath: 'id' }— ключ считывается из свойстваidкаждого сохраняемого object'а (встроенный ключ).{ keyPath: 'id', autoIncrement: true }— хранилище автоматически генерирует последовательный ключ, если он не указан.{ autoIncrement: true }— ключи генерируются и хранятся отдельно от значения (внешний ключ).
Также внутри onupgradeneeded создаются индексы. Индекс позволяет искать записи по свойству, отличному от первичного ключа. Передайте { unique: true }, чтобы запретить дублирование значений этого свойства.
Транзакции
После настройки базы данных и объектных хранилищ для управления операциями с данными необходимо использовать транзакции. Транзакция в IndexedDB — это механизм, группирующий несколько операций в единицу работы, которая либо полностью завершается успешно, либо полностью откатывается. Это необходимо для обеспечения целостности данных, особенно когда несколько операций зависят друг от друга для получения корректного результата.
Как использовать транзакции в IndexedDB
Шаг 1: Начало транзакции
Для выполнения любой операции в IndexedDB начните с создания транзакции для базы данных. Транзакция создаётся путём указания объектных хранилищ, в которых она будет работать, и режима транзакции: "readonly" или "readwrite".
Шаг 2: Доступ к объектному хранилищу
Внутри транзакции можно обращаться к одному или нескольким объектным хранилищам для выполнения операций с данными.
Шаг 3: Выполнение операций
Получив доступ к объектному хранилищу, вы можете выполнять различные операции: добавление, получение, обновление или удаление данных. Каждая операция возвращает объект запроса, который можно использовать для обработки событий успеха или ошибки.
Шаг 4: Завершение транзакции
Транзакция автоматически завершается, когда все выданные в ней операции выполнены — успешно или с ошибкой. Также можно прослушивать событие complete на транзакции, чтобы выполнить действия после успешного завершения всех операций.
Вот пример полной транзакции:
Чтение данных
Для получения данных используйте store.get(key) или курсор для итерации по нескольким записям. Вот краткие примеры:
Получение одной записи:
Итерация с курсором:
Запросы через индексы
Первичный ключ позволяет получить запись, когда ключ уже известен. Индекс позволяет искать по другому свойству — например, найти книгу по её title. Получите доступ к индексу через store.index(name), затем вызывайте на нём get(), getAll() или openCursor() — так же, как на хранилище.
Поиск записи по индексированному свойству:
Также можно ограничить запрос диапазоном ключей с помощью IDBKeyRange (например, IDBKeyRange.bound('A', 'M') для получения всех книг, название которых начинается между A и M), передавая диапазон в getAll() или openCursor().
Обновление и удаление записей
Для изменения существующих данных используйте store.put() с тем же ключом. Для удаления данных используйте store.delete(key).
Обновление записи:
Удаление записи:
Как просмотреть содержимое IndexedDB в браузере
После сохранения и обработки данных может потребоваться проверить, что именно хранится в браузере. Большинство современных браузеров отображают содержимое IndexedDB. Ниже приведён пример из инструментов разработчика Chrome:

Лучшие практики использования транзакций
Чтобы реализация IndexedDB оставалась надёжной и производительной, следуйте этим рекомендациям:
- Минимизируйте область действия: держите транзакции как можно меньшими — и по количеству операций, и по продолжительности. Это снижает вероятность конфликтов и повышает производительность.
- Обработка ошибок: всегда реализуйте обработку ошибок как на уровне запроса, так и на уровне транзакции. Это помогает диагностировать проблемы и предотвращать частичные обновления, которые могут привести к повреждению данных.
- Конкурентность: учитывайте, что хотя IndexedDB является асинхронным и неблокирующим, транзакции к одной базе данных ставятся в очередь и выполняются последовательно, чтобы предотвратить гонку данных и несоответствия.
Заключение
IndexedDB предоставляет надёжную платформу для сложного управления данными в веб-приложениях, делая его незаменимым инструментом для современных веб-разработчиков. При правильной реализации его возможностей разработчики могут эффективно хранить, получать, обновлять и удалять клиентские данные, повышая производительность приложения и удобство работы пользователей.
Для более простых потребностей в хранении данных и понимания общей картины хранилищ браузера ознакомьтесь со связанными главами:
- localStorage и sessionStorage — облегчённое хранилище «ключ/значение» для небольших объёмов string-данных.
- Storage API — как браузер управляет квотами хранилища и персистентностью между механизмами.
- Работа с JSON — сериализация данных, полезная при перемещении object'ов в хранилище и из него.