Стратегия fallback на localStorage

Idb-keyval представляет собой минималистичную библиотеку для работы с IndexedDB в браузере. Основная цель — упростить асинхронное хранение ключ-значение без необходимости писать громоздкие промисы и обработчики ошибок для самой IndexedDB. Однако в некоторых случаях IndexedDB может быть недоступна: например, в приватных режимах браузера или при ограничениях платформы. В таких ситуациях полезно реализовать fallback на localStorage, чтобы данные не терялись.


Основы использования Idb-keyval

Библиотека предоставляет следующие ключевые функции:

  • set(key, value): сохраняет значение value по ключу key.
  • get(key): получает значение по ключу key.
  • del(key): удаляет значение по ключу key.
  • clear(): очищает все хранилище.
  • keys(): возвращает массив всех ключей.

Пример базового использования:

import { set, get } from 'idb-keyval';

await set('user', { name: 'Alice', age: 25 });
const user = await get('user');
console.log(user); // { name: 'Alice', age: 25 }

Все операции возвращают промисы, что позволяет использовать async/await или цепочки .then().


Проблема недоступности IndexedDB

Некоторые браузеры в приватных режимах или при строгих настройках блокируют IndexedDB. В этом случае вызовы set или get могут вызывать ошибки или просто не сохранять данные. Чтобы избежать потери данных, реализуется fallback на localStorage:

  • localStorage синхронное хранилище, ограниченное примерно 5–10 МБ.
  • Используется как резервный вариант, когда IndexedDB недоступна.
  • Позволяет работать с теми же ключами и значениями, хотя и без асинхронности.

Реализация fallback

Для корректного fallback необходимо обернуть все операции Idb-keyval в функции, проверяющие доступность IndexedDB:

import { set as idbSet, get as idbGet } from 'idb-keyval';

function isIndexedDBAvailable() {
    try {
        return !!window.indexedDB;
    } catch {
        return false;
    }
}

async function setItem(key, value) {
    if (isIndexedDBAvailable()) {
        try {
            await idbSet(key, value);
            return;
        } catch (e) {
            console.warn('IndexedDB недоступна, сохраняем в localStorage', e);
        }
    }
    localStorage.setItem(key, JSON.stringify(value));
}

async function getItem(key) {
    if (isIndexedDBAvailable()) {
        try {
            const value = await idbGet(key);
            if (value !== undefined) return value;
        } catch (e) {
            console.warn('Ошибка при чтении из IndexedDB', e);
        }
    }
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : undefined;
}

Ключевые моменты:

  • Проверка доступности IndexedDB с помощью try/catch.
  • Обработка ошибок при работе с Idb-keyval.
  • Использование JSON.stringify/JSON.parse для совместимости с localStorage.

Синхронизация данных

Когда fallback срабатывает, возникает проблема синхронизации: данные, сохранённые в localStorage, могут устареть, если позже IndexedDB становится доступна. Стратегии синхронизации:

  1. Мгновенная миграция: при инициализации приложения проверять localStorage и переносить данные в IndexedDB.
async function migrateToIDB() {
    if (!isIndexedDBAvailable()) return;

    for (let i = 0; i < localStorage.length; i++) {
        const key = localStorage.key(i);
        const value = JSON.parse(localStorage.getItem(key));
        await idbSet(key, value);
    }
    localStorage.clear();
}
  1. Конфликтное разрешение: сохранять метаданные с отметкой времени, чтобы при синхронизации использовать более свежие данные.
  2. Стратегия «последнее сохранённое значение»: при конфликте выбирается последнее по времени обновления.

Поддержка всех операций Idb-keyval с fallback

Для полноценного использования fallback можно обернуть все методы:

async function delItem(key) {
    if (isIndexedDBAvailable()) {
        try {
            await idbDel(key);
        } catch {
            localStorage.removeItem(key);
        }
    } else {
        localStorage.removeItem(key);
    }
}

async function clearAll() {
    if (isIndexedDBAvailable()) {
        try {
            await idbClear();
        } catch {
            localStorage.clear();
        }
    } else {
        localStorage.clear();
    }
}

async function getAllKeys() {
    if (isIndexedDBAvailable()) {
        try {
            return await idbKeys();
        } catch {
            return Object.keys(localStorage);
        }
    } else {
        return Object.keys(localStorage);
    }
}

Практические рекомендации

  • Использовать localStorage только для небольших данных и временных fallback-значений.
  • Всегда оборачивать операции в try/catch для предотвращения неожиданных ошибок.
  • При синхронизации данных предусматривать стратегию разрешения конфликтов.
  • Не хранить в localStorage объекты с большими бинарными данными — это может быстро исчерпать лимит.

Эта стратегия позволяет сочетать преимущества IndexedDB — асинхронность и большой объём хранилища — с надёжностью localStorage как резервного варианта. Такой подход обеспечивает стабильность приложения в любых условиях работы браузера.