Как вы уже, наверное, знаете, браузерные хранилища данных, такие как localStorage и sessionStorage, сильно ограничены в своих размерах и для хранения большого количества данных не подходят. В разных браузерах этот размер варьируется, но в среднем принято считать, что это около 5 МБ.

Что же делать, если нужно больше ? Для этой ситуации в браузере предусмотрен другой Web API - IndexedDB. Его размеры и регулирование также сильно варьируются от браузера к браузеру, но обычно это около 50% доступного дискового пространства на устройстве пользователя.

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

К счастью, для нас существует большое количество библиотек, которые упрощают взаимодействие с этим хранилищем в разы. Вот некоторые из них:

  1. Dexie.js

  2. idb

  3. localForage

Нас больше интересует последний вариант - localForage. Он в отличие от остальных библиотек реализует API очень близкий по реализации localStorage, что ещё больше упрощает работу для решения большинства задач.

Перед тем как мы перейдём к его подробному разбору, хотелось бы дополнительно отметить несколько дополнительных преимуществ перед стандартным localStorage, помимо увеличенного размера хранилища:

  1. Асинхронный API - при частых операциях с данными наш поток не будет забиваться как в случае с localStorage, все задачи выполняются отложено.

  2. Формат хранения данных - localForage поддерживает любой формат данных (объекты, массивы, бинарные данные и так далее) и не требует предварительной сериализации / десериализации данных, так как под капотом работает IndexedDB, который умеет это делать из коробки. Это удобно и не требует дополнительных вычислительных трат на преобразование.

Итак, приступим к разбору.

Установка

# Eсли у вас npm
npm install localforage

# Если у вас yarn
yarn add localforage

Также можно создать глобальную переменную при помощи ручной установки тега script в коде:

<script src="localforage/dist/localforage.js"></script>
<script>console.log(localforage)</script>

Основные методы

Библиотека предлагает 2 варианта обработки операций: при помощи колбэков и при помощи промисов. Далее на примере метода getItem можно будет увидеть их оба.

getItem

getItem(key, callback)

Получает содержимое хранилища по ключу key и передаёт его в callback.

Если ранее был сохранён undefined по определённому ключу, то при получении этого элемента при помощи getItem() будет возвращён null. Это сделано по причинам совместимости localForage и ограничений localStorage.

localforage.getItem('somekey').then((value) => {
    console.log(value);
});
localforage.getItem('somekey', (error, value) => {
    console.log(value);
});

setItem

setItem(key, value, callback)  

Сохраняет данные value по заданному ключу key и вызывает callback.

localforage.setItem('somekey', 'some value').then((value) => {
    console.log(value);
});

removeItem

removeItem(key, callback)

Удаляет данные по ключу key и вызывает callback.

localforage.removeItem('somekey').then(() => {
    console.log('Ключ somekey был успешно удалён.');
});

clear

clear(callback)

Удаляет все ключи из базы данных.

localforage.clear().then(() => {
    console.log('База данных была очищена.');
});

length

length(callback)

Возвращает количество записей в хранилище и вызывает callback.

localforage.length().then((numberOfKeys) => {
    console.log(numberOfKeys);
});

key

key(keyIndex, callback)

Получает имя ключа по его индексу (порядковому номеру) и передаёт его в callback.

localforage.key(2).then((keyName) => {
    console.log(keyName);
});

keys

keys(callback)

Возвращает список всех ключей хранилища и передаёт их в callback.

localforage.keys().then((keys) => {
    console.log(keys);
});

iterate

iterate(iteratorCallback, callback)

Проходит по всем элементам в хранилище используя функцию iteratorCallback и возвращает результат в callback.

localforage.iterate((value, key, iterationNumber) => {
    console.log([key, value]);
});

Драйверы

Под драйверами в библиотеке подразумеваются типы хранилищ, используемых для записи данных. По-умолчанию их 3: IndexedDB, WebSQL (устарел и не рекомендуется к использованию) и localStorage. Если явно не указать необходимый драйвер, то localForage автоматически его выберет на основании поддержки браузером того или иного API в следующем порядке:

  1. IndexedDB

  2. WebSQL

  3. localStorage

Мы можем управлять этим поведением при помощи метода setDriver.

setDriver(driverName)
setDriver([driverName, nextDriverName])
// Устанавливаем один предпочтительный драйвер
localforage.setDriver(localforage.LOCALSTORAGE);

// Устанавливаем несколько драйверов на случай, если один или несколько из них не будут поддерживаться в браузере
localforage.setDriver([localforage.WEBSQL, localforage.INDEXEDDB]);

Также при помощи метода defineDriver мы можем объявлять свои собственные драйверы.

Конфигурация

Каждому хранилищу, создаваемому при помощи localForage можно задавать определённые настройки при помощи конфигурации.

localforage.config({
    driver: localforage.INDEXEDDB, // Указывает, какой драйвер использовать. Может быть: localforage.LOCALSTORAGE, localforage.INDEXEDDB или localforage.WEBSQL
    name: 'myApp', // Имя базы данных (используется для идентификации хранилища)
    version: 1.0, // Версия базы данных
    description: 'My App storage', // Описание базы данных
    size: 4980736, // Размер хранилища в байтах для WebSQL (по умолчанию — 5MB)
});

Несколько хранилищ

Для работы сразу с несколькими хранилищами сразу мы можем использовать методы:

  1. createInstance - для создания нового инстанса

  2. dropInstance - для удаления существующего

// Создаем первый инстанс с уникальным именем 'userData'
const userDataStore = localforage.createInstance({
    name: 'myApp',
    storeName: 'userData',
    driver: localforage.INDEXEDDB,
});

// Записываем в него какие-то данные
userDataStore.setItem('username', 'Вася').then(() => {
    console.log('Данные пользователя сохранены');
});


// Создаем второй инстанс с уникальным именем 'appSettings'
const appSettingsStore = localforage.createInstance({
    name: 'myApp',
    storeName: 'appSettings', 
    driver: localforage.LOCALSTORAGE, 
});

// Также записываем в него какие-то данные
appSettingsStore.setItem('theme', 'dark').then(() => {
    console.log('Настройки приложения сохранены.');
});


// Удаляем их оба
userDataStore.dropInstance().then(() => {
  console.log('userDataStore был удалён')
});

appSettingsStore.dropInstance().then(() => {
  console.log('appSettingsStore был удалён')
});

Дополнительно

Думаю на данном этапе у вас уже сложилась картина как устроена библиотека. Методы, которые я не перечислил здесь, вы можете самостоятельно найти в официальной документации.

Комментарии (10)


  1. Nn111
    20.01.2025 13:14

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

    Просто расширения для браузеров я довольно давно пишу и ни разу не упирался в потолок localstorage.


    1. euhoo
      20.01.2025 13:14


      В local-storage хранили словарики. В какой-то момент стало не хватать, перешли на ng-forage - обертка local-forage для ангуляра.
      офтоп. У нас проект на ангуляре. В какой-то момент ng-forage перестал поддерживать поднятие версий ангуляра. На нативную indexedDb переходить не хотелось, прослойку в виде local-forage тоже особо не хотелось. В итоге скопипастил из local-forage несколько методов вместе с реализацией и этого хватило.


      1. Viktor9354 Автор
        20.01.2025 13:14

        Интересно узнать, почему решили словарики перенести в браузерные хранилища ?


        1. euhoo
          20.01.2025 13:14

          так кончилось место в local-storage


          1. Viktor9354 Автор
            20.01.2025 13:14

            Да, понял, непонятно почему бы их не хранить просто ввиде большого объекта в файле ? Или ввиде большого количества подобных объектов, разбросанных по проекту.


            1. euhoo
              20.01.2025 13:14

              Словарики с бэка приходят и периодически обновляются.

              Можно было бы хранить в памяти где-нибудь в redux(ngrx), но если кратко - не хочется терять кэш словарей при перезагрузке страницы. Поэтому indexedDB, а не redux(ngrx в нашем случае ангуляра)


              1. NetInstant1100
                20.01.2025 13:14

                Если посмотреть разные вариации i18n, то довольно часто они сами все это реализовывают и не совсем понятно зачем что-то класть в redux.

                Файлы прекрасно кешируются браузером, и если у вас переводы не обновляются по несколько раз в час - это не должно быть проблемой.


                1. Viktor9354 Автор
                  20.01.2025 13:14

                  Хороший вариант, согласен


    1. starfair
      20.01.2025 13:14

      Пожалуйста: есть сейчас такой офисный пакет, импортозамещаемый, называемый Р7. ПОд капотом у него бинарное ядро обработки самого документа, и фронтендом в качестве GUI - Chromium. Сразу понятно, что раз это по факту браузер, то о работе с внешними хранилищами данных, как это можно было делать через ADO или ODBC, напрямую работать не получится. А такое решение, вполне себе позволяет прикрутить хоть как то похожее на локальную СУБД, с помощью плагинов, в состав Р7. И других вариантов, без превлечения санитаров, самописных серверов для работы с данными, там фактически и нет.


  1. Viktor9354 Автор
    20.01.2025 13:14

    Спасибо за комментарий !

    Для сохранения больших черновиков отредактированных данных подходит, особенно когда работаешь с Data Science проектами или редакторами текста, кода и пр. Но это из личного опыта, а так вообще применений много. Знаю что такие штуки используются в играх, онлайн-картах и других приложениях, в которых необходимо хранить большое количество данных локально, чтобы каждый раз не ходить за ними на сервер или потому что на сервере их хранить необязательно.