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 и другие хранилища #
| IndexedDB | localStorage | Cookies | |
|---|---|---|---|
| API | Асинхронный | Синхронный | Строка document.cookie |
| Объём | Существенно больше | ~5 МБ | Килобайты |
| Структура | Объекты, индексы | Строки | Пары имя=значение |
| Отправка на сервер | Нет | Нет | Да, с запросами |
Настройки UI по-прежнему удобнее хранить в Web Storage; сессионные токены — в HttpOnly cookies. IndexedDB — для объёмного клиентского кэша и офлайн-first логики.
Частые ошибки #
- Создают store вне
upgradeneeded— вызовcreateObjectStoreвonsuccessбросит исключение. - Закрывают db до завершения транзакции — дождитесь
tx.oncomplete. - Хранят DOM-узлы или функции — IndexedDB использует structured clone; не всё сериализуется.
- Игнорируют приватный режим — браузер может отказать в persistent storage; обрабатывайте
quotaи fallback.
Итог #
indexedDB.open(name, version)открывает базу; схема меняется вupgradeneeded.- Данные лежат в object store; запись —
put/add, чтение —get/getAll. - Индексы ускоряют поиск по полям; курсоры обходят большие выборки по одной записи.
- API полностью асинхронный — оборачивайте в Promise или используйте обёртки вроде
idb. - Для простых строк и настроек достаточно localStorage; IndexedDB — для структурированного кэша.