IndexedDB

IndexedDB #

IndexedDB — встроенная в браузер объектная база данных для структурированных записей, файлов и больших массивов. В отличие от localStorage, API асинхронный, лимиты выше, а данные индексируются по ключам. Подходит для офлайн-черновиков, кэша плейлистов и очереди синхронизации — когда Web Storage уже тесен.


Открытие базы #

Работа идёт через indexedDB.open. Имя и версия задают схему; при повышении версии срабатывает upgradeneeded:

const request = indexedDB.open('jsflow-app', 1)

request.onupgradeneeded = (event) => {
  const db = event.target.result
  if (!db.objectStoreNames.contains('tracks')) {
    db.createObjectStore('tracks', { keyPath: 'id' })
  }
}

request.onsuccess = (event) => {
  const db = event.target.result
  console.log('База готова', db.name, db.version)
}

request.onerror = () => {
  console.error('Не удалось открыть IndexedDB')
}

В upgradeneeded создают object store (аналог таблицы) и индексы. Вне этой фазы схему менять нельзя — только bump версии.


Object store: put и get #

Транзакция группирует операции чтения или записи:

function saveTrack(db, track) {
  return new Promise((resolve, reject) => {
    const tx = db.transaction('tracks', 'readwrite')
    const store = tx.objectStore('tracks')
    const req = store.put(track) // insert или update по keyPath

    req.onsuccess = () => resolve(req.result)
    req.onerror = () => reject(req.error)
  })
}

async function loadTrack(db, id) {
  const tx = db.transaction('tracks', 'readonly')
  const store = tx.objectStore('tracks')
  return new Promise((resolve, reject) => {
    const req = store.get(id)
    req.onsuccess = () => resolve(req.result)
    req.onerror = () => reject(req.error)
  })
}

await saveTrack(db, { id: 'trk-91', title: 'Night Drive', bpm: 118 })
const cached = await loadTrack(db, 'trk-91')

put перезаписывает запись с тем же ключом; add — только если ключа ещё нет (иначе ошибка).


Индексы и выборка #

Индекс ускоряет поиск по полю, которое не является primary key:

// в onupgradeneeded:
const store = db.createObjectStore('tracks', { keyPath: 'id' })
store.createIndex('byGenre', 'genre', { unique: false })

// чтение по индексу
const tx = db.transaction('tracks', 'readonly')
const index = tx.objectStore('tracks').index('byGenre')
const req = index.getAll('electronic')
req.onsuccess = () => console.log(req.result.length)

getAll возвращает массив; для больших наборов лучше курсор.


Курсор — обход по частям #

Курсор идёт по store или индексу без загрузки всего набора в память:

function forEachTrack(db, onRow) {
  return new Promise((resolve, reject) => {
    const tx = db.transaction('tracks', 'readonly')
    const store = tx.objectStore('tracks')
    const req = store.openCursor()

    req.onsuccess = (event) => {
      const cursor = event.target.result
      if (!cursor) {
        resolve()
        return
      }
      onRow(cursor.value)
      cursor.continue()
    }

    req.onerror = () => reject(req.error)
  })
}

Методы advance(n) и continue(key) управляют позицией. Для фильтрации по диапазону ключей используйте IDBKeyRange.bound() / only().


Удаление и очистка #

const tx = db.transaction('tracks', 'readwrite')
tx.objectStore('tracks').delete('trk-91')
// tx.objectStore('tracks').clear() — удалить все записи store

Транзакция commit-ится автоматически, когда все запросы завершены без ошибок; при abort изменения откатываются.


IndexedDB и другие хранилища #

IndexedDBlocalStorageCookies
APIАсинхронныйСинхронныйСтрока document.cookie
ОбъёмСущественно больше~5 МБКилобайты
СтруктураОбъекты, индексыСтрокиПары имя=значение
Отправка на серверНетНетДа, с запросами

Настройки UI по-прежнему удобнее хранить в Web Storage; сессионные токены — в HttpOnly cookies. IndexedDB — для объёмного клиентского кэша и офлайн-first логики.


Частые ошибки #

  1. Создают store вне upgradeneeded — вызов createObjectStore в onsuccess бросит исключение.
  2. Закрывают db до завершения транзакции — дождитесь tx.oncomplete.
  3. Хранят DOM-узлы или функции — IndexedDB использует structured clone; не всё сериализуется.
  4. Игнорируют приватный режим — браузер может отказать в persistent storage; обрабатывайте quota и fallback.

Итог #

  • indexedDB.open(name, version) открывает базу; схема меняется в upgradeneeded.
  • Данные лежат в object store; запись — put/add, чтение — get/getAll.
  • Индексы ускоряют поиск по полям; курсоры обходят большие выборки по одной записи.
  • API полностью асинхронный — оборачивайте в Promise или используйте обёртки вроде idb.
  • Для простых строк и настроек достаточно localStorage; IndexedDB — для структурированного кэша.

См. также #