Как вы уже, наверное, знаете, браузерные хранилища данных, такие как localStorage и sessionStorage, сильно ограничены в своих размерах и для хранения большого количества данных не подходят. В разных браузерах этот размер варьируется, но в среднем принято считать, что это около 5 МБ.
Что же делать, если нужно больше ? Для этой ситуации в браузере предусмотрен другой Web API - IndexedDB. Его размеры и регулирование также сильно варьируются от браузера к браузеру, но обычно это около 50% доступного дискового пространства на устройстве пользователя.
Сам этот API достаточно неудобен в использовании, так как предполагает работу с большим количеством объёмных конструкций в коде и без использования заранее подготовленных собственных утилит не представляется возможным.
К счастью, для нас существует большое количество библиотек, которые упрощают взаимодействие с этим хранилищем в разы. Вот некоторые из них:
Dexie.js
idb
localForage
Нас больше интересует последний вариант - localForage. Он в отличие от остальных библиотек реализует API очень близкий по реализации localStorage, что ещё больше упрощает работу для решения большинства задач.
Перед тем как мы перейдём к его подробному разбору, хотелось бы дополнительно отметить несколько дополнительных преимуществ перед стандартным localStorage, помимо увеличенного размера хранилища:
Асинхронный API - при частых операциях с данными наш поток не будет забиваться как в случае с localStorage, все задачи выполняются отложено.
Формат хранения данных - 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 в следующем порядке:
IndexedDB
WebSQL
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)
});
Несколько хранилищ
Для работы сразу с несколькими хранилищами сразу мы можем использовать методы:
createInstance - для создания нового инстанса
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 был удалён')
});
Дополнительно
Думаю на данном этапе у вас уже сложилась картина как устроена библиотека. Методы, которые я не перечислил здесь, вы можете самостоятельно найти в официальной документации.
Комментарии (11)
Viktor9354 Автор
20.01.2025 13:14Спасибо за комментарий !
Для сохранения больших черновиков отредактированных данных подходит, особенно когда работаешь с Data Science проектами или редакторами текста, кода и пр. Но это из личного опыта, а так вообще применений много. Знаю что такие штуки используются в играх, онлайн-картах и других приложениях, в которых необходимо хранить большое количество данных локально, чтобы каждый раз не ходить за ними на сервер или потому что на сервере их хранить необязательно.
RegIon
20.01.2025 13:14CacheAPI спокойной позволяет загнать блоб и прочитать его как блоб, так как по сути работает на Request/Response
Nn111
Не подумайте что придираюсь, но можете пожалуйста дать хотя бы один пример, где это надо? Только из вашей реальной работы, а не выдуманный?
Просто расширения для браузеров я довольно давно пишу и ни разу не упирался в потолок localstorage.
euhoo
В local-storage хранили словарики. В какой-то момент стало не хватать, перешли на ng-forage - обертка local-forage для ангуляра.
офтоп. У нас проект на ангуляре. В какой-то момент ng-forage перестал поддерживать поднятие версий ангуляра. На нативную indexedDb переходить не хотелось, прослойку в виде local-forage тоже особо не хотелось. В итоге скопипастил из local-forage несколько методов вместе с реализацией и этого хватило.
Viktor9354 Автор
Интересно узнать, почему решили словарики перенести в браузерные хранилища ?
euhoo
так кончилось место в local-storage
Viktor9354 Автор
Да, понял, непонятно почему бы их не хранить просто ввиде большого объекта в файле ? Или ввиде большого количества подобных объектов, разбросанных по проекту.
euhoo
Словарики с бэка приходят и периодически обновляются.
Можно было бы хранить в памяти где-нибудь в redux(ngrx), но если кратко - не хочется терять кэш словарей при перезагрузке страницы. Поэтому indexedDB, а не redux(ngrx в нашем случае ангуляра)
NetInstant1100
Если посмотреть разные вариации i18n, то довольно часто они сами все это реализовывают и не совсем понятно зачем что-то класть в redux.
Файлы прекрасно кешируются браузером, и если у вас переводы не обновляются по несколько раз в час - это не должно быть проблемой.
Viktor9354 Автор
Хороший вариант, согласен
starfair
Пожалуйста: есть сейчас такой офисный пакет, импортозамещаемый, называемый Р7. ПОд капотом у него бинарное ядро обработки самого документа, и фронтендом в качестве GUI - Chromium. Сразу понятно, что раз это по факту браузер, то о работе с внешними хранилищами данных, как это можно было делать через ADO или ODBC, напрямую работать не получится. А такое решение, вполне себе позволяет прикрутить хоть как то похожее на локальную СУБД, с помощью плагинов, в состав Р7. И других вариантов, без превлечения
санитаров, самописных серверов для работы с данными, там фактически и нет.