Существует множество способов хранения данных в браузере. Какой из них лучше всего подходит для ваших нужд?
Подключение к интернету в дороге может быть нестабильным или отсутствовать, поэтому поддержка офлайн-режима и надежная работа – это стандартные функции прогрессивных веб-приложений. Даже в идеальных условиях беспроводной связи разумное использование кеширования и других методов хранения может существенно улучшить пользовательский опыт. Существует несколько способов кеширования статических ресурсов приложения (HTML, JavaScript, CSS, изображений и т. д.) и данных (пользовательских данных, новостных статей и т. д.). Но какое решение лучше? Сколько вы можете хранить? Как предотвратить выселение?
Что выбрать?
Вот общие рекомендации по хранению ресурсов:
- Для сетевых ресурсов, необходимых для загрузки приложения, используйте Cache Storage API (часть сервис-воркеров).
- Для контента на основе файлов используйте Origin Private File System (OPFS).
- Для других данных используйте IndexedDB (с оболочкой promises).
IndexedDB, OPFS и Cache Storage API поддерживаются во всех современных браузерах.
Они асинхронны и не блокируют основной поток (но есть и синхронный вариант OPFS, доступный только в веб-воркерах). Они доступны из объекта window, веб-воркеров и сервисных воркеров, что позволяет использовать их в любом месте кода.
Что насчет других механизмов хранения?
В браузере есть и другие механизмы хранения, но они имеют ограниченное применение и могут вызывать значительные проблемы с производительностью.
SessionStorage относится к конкретной вкладке и действует в течение времени ее существования. Это может быть полезно для хранения небольших объемов информации, относящейся к сеансу, например ключа IndexedDB. Используйте его с осторожностью, поскольку он является синхронным и блокирует основной поток. Его размер ограничен примерно 5 МБ, и он может содержать только строки. Поскольку он зависит от вкладки, он недоступен для веб-воркеров или сервис-воркеров.
Не рекомендуется использовать LocalStorage, поскольку это синхронный метод, который блокирует основной поток. Его размер ограничен примерно 5 МБ, и он может содержать только строки. LocalStorage недоступен для веб-воркеров и сервис-воркеров.
Файлы cookie имеют свое назначение, но не должны использоваться для хранения данных. Файлы cookie отправляются с каждым HTTP-запросом, поэтому хранение чего-либо большего, чем небольшой объем данных, значительно увеличит размер каждого веб-запроса. Они синхронны и недоступны из веб-воркеров. Как и LocalStorage и SessionStorage, файлы cookie ограничены только строками.
File System Access API позволяет пользователям читать и редактировать файлы в локальной файловой системе. Пользователь должен предоставить разрешение, прежде чем страница сможет читать или записывать в любой локальный файл, и разрешения не сохраняются между сеансами, если только дескриптор файла не кэшируется в IndexedDB. File System Access API лучше всего подходит для таких сценариев, как редакторы, где вам нужно открыть файл, изменить его, а затем, возможно, сохранить изменения обратно в файл.
File System API и FileWriter API предоставляют методы для чтения и записи файлов в изолированной файловой системе. Хотя он асинхронный, мы не рекомендуем его использовать, поскольку он доступен только в браузерах на основе Chromium.
Сколько данных можно хранить?
Короткий ответ: много. Как минимум несколько сотен мегабайт, а потенциально – сотни гигабайт или даже больше. Реализации браузеров различаются, но объем доступного хранилища обычно зависит от объема хранилища, доступного на устройстве.
- Chrome позволяет браузеру использовать до 80 % от общего объема места на диске. Источник может использовать до 60 % от общего объема места на диске. Чтобы определить максимальную доступную квоту, можно использовать StorageManager API. В других браузерах на основе Chromium настройки могут отличаться.
- В режиме инкогнито Chrome уменьшает объем хранилища, который может использовать источник, примерно до 5 % от общего места на диске.
- Если пользователь включил в Chrome параметр "Удалять файлы cookie и данные сайтов при закрытии всех окон", квота на хранение данных значительно уменьшается – примерно до 300 МБ.
- Firefox позволяет браузеру использовать до 50 % свободного места на диске. Группа eTLD+1 (например,
example.com,www.example.comиfoo.bar.example.com) может использовать до 2 ГБ. Чтобы узнать, сколько места осталось, используйте StorageManager API. - Safari (как на компьютерах, так и на мобильных устройствах) позволяет хранить около 1 ГБ. Когда лимит будет достигнут, Safari предложит пользователю увеличить его на 200 МБ. Мне не удалось найти официальную документацию по этому вопросу.
- Если PWA добавлено на главный экран в мобильной версии Safari, оно создает новый контейнер хранилища, и между PWA и мобильной версией Safari ничего не передается. Если квота для установленного PWA исчерпана, запросить дополнительное пространство для хранения данных нельзя.
Раньше, если сайт превышал определенный порог хранения данных, браузер запрашивал у пользователя разрешение на использование большего объема данных. Например, если источник использовал более 50 МБ, браузер запрашивал у пользователя разрешение на хранение до 100 МБ, а затем снова запрашивал разрешение на увеличение объема с шагом 50 МБ.
В настоящее время большинство современных браузеров не запрашивают у пользователя разрешение и позволяют сайту использовать выделенную квоту. Исключением является Safari, который запрашивает разрешение на увеличение выделенной квоты при ее превышении. Если источник попытается использовать больше выделенной квоты, дальнейшие попытки записи данных будут неудачными.
Как узнать, сколько места в хранилище доступно?
Во многих браузерах вы можете использовать StorageManager API, чтобы определить, сколько места доступно источнику и сколько он использует. Он сообщает общее количество байтов, используемых IndexedDB и Cache API, и позволяет рассчитать приблизительный объем оставшегося доступного пространства для хранения.
if (navigator.storage && navigator.storage.estimate) {
const quota = await navigator.storage.estimate();
// quota.usage -> Number of bytes used.
// quota.quota -> Maximum number of bytes available.
const percentageUsed = (quota.usage / quota.quota) * 100;
console.log(`You've used ${percentageUsed}% of the available storage.`);
const remaining = quota.quota - quota.usage;
console.log(`You can write up to ${remaining} more bytes.`);
}
Вы должны отслеживать ошибки, связанные с превышением квоты (см. ниже). В некоторых случаях доступная квота может превышать фактический объем доступного хранилища.
Проверить
В процессе разработки вы можете использовать инструменты разработчика в браузере, чтобы проверять разные типы хранилищ и удалять все сохраненные данные.
В Chrome 88 добавлена новая функция, которая позволяет переопределить квоту на хранение данных сайта на панели "Хранилище". Эта функция позволяет симулировать разные устройства и тестировать поведение приложений в условиях нехватки места на диске. Перейдите в раздел Application (Приложение), а затем – в Storage (Хранилище). Установите флажок Simulate custom storage quota (Имитировать пользовательскую квоту на хранение данных) и введите любое допустимое число, чтобы имитировать квоту на хранение данных.
Что делать, если квота превышена?
Что делать, если вы превысили квоту Самое главное – всегда отслеживать и обрабатывать ошибки записи, будь то QuotaExceededError или что-то другое. Затем, в зависимости от дизайна приложения, решите, как его обрабатывать.
Например, удаляйте контент, к которому давно не обращались, или данные, размер которых превышает определенное значение. Также можно предоставить пользователям возможность самостоятельно выбирать, что удалять.
Если вы превысите доступную квоту, IndexedDB и Cache API выдадут ошибку DOMError с названием QuotaExceededError.
IndexedDB
Если квота превышена, попытки записи в IndexedDB будут неудачными. Будет вызван обработчик onabort() транзакции, передающий событие.
Событие будет содержать DOMException в свойстве error. Проверка ошибки name вернет QuotaExceededError.
const transaction = idb.transaction(['entries'], 'readwrite');
transaction.onabort = function(event) {
const error = event.target.error; // DOMException
if (error.name == 'QuotaExceededError') {
// Fallback code goes here
}
};
Cache API
Если квота превышена, попытки записи в Cache API будут отклоняться с ошибкой QuotaExceededErrorDOMException.
try {
const cache = await caches.open('my-cache');
await cache.add(new Request('/sample1.jpg'));
} catch (err) {
if (error.name === 'QuotaExceededError') {
// Fallback code goes here
}
}
Как работает выселение?
Веб-хранилище делится на две категории: "По возможности" и "Постоянное". В первом случае браузер может очистить хранилище, не прерывая работу пользователя, но оно менее надежно для долгосрочного хранения или критически важных данных. Постоянное хранилище не очищается автоматически, когда в нем мало места. Пользователь должен вручную очистить это хранилище (через настройки браузера).
По умолчанию данные сайтов (включая IndexedDB, Cache API и т. д.) попадают в категорию "по возможности". Это означает, что если сайт не запросил постоянное хранилище, браузер может удалить данные сайтов по своему усмотрению, например когда на хранилище устройства мало места.
Правила вытеснения для этого режима:
- Браузеры на основе Chromium начинают удалять данные, когда в них заканчивается место. Сначала удаляются все данные сайтов, которые использовались раньше всего, затем – следующие и так далее, пока не будет освобождено достаточно места.
- Firefox начнет вытеснять данные, когда доступное место на диске будет заполнено, очищая все данные сайтов из источника, который использовался реже всего, затем из следующего, пока браузер не перестанет превышать лимит.
- В Safari ранее не удалялись данные, но недавно было введено ограничение на хранение данных в течение семи дней (см. ниже).
Начиная с iOS и iPadOS 13.4 и Safari 13.1 на macOS, для всех хранилищ, доступных для записи скриптов, включая IndexedDB, регистрацию сервис-воркеров и Cache API, установлено ограничение в семь дней. Это означает, что Safari удалит весь контент из кеша после семи дней использования Safari, если пользователь не взаимодействует с сайтом. Это правило не применяется к установленным PWA, которые были добавлены на главный экран. Подробную информацию можно найти в статье Полная блокировка сторонних файлов cookie и другие изменения в блоге WebKit.
Сегменты хранилища
Основная идея Storage Buckets API заключается в том, чтобы предоставить сайтам возможность создавать несколько сегментов хранилища, которые браузер может удалять независимо друг от друга. Это позволяет разработчикам указывать приоритет вытеснения, чтобы гарантировать, что наиболее ценные данные не будут удалены.
Бонус: зачем использовать оболочку для IndexedDB
IndexedDB – это низкоуровневый API, который требует значительной настройки перед использованием, что может быть особенно неудобно для хранения данных низкой сложности. В отличие от большинства современных API, основанных на обещаниях, он основан на событиях. Обертки Promise, такие как idb для IndexedDB, скрывают некоторые мощные функции, но, что более важно, скрывают сложные механизмы (например, транзакции, управление версиями схемы), которые поставляются с библиотекой IndexedDB.
Бонус: SQLite Wasm
После того как поддержка Web SQL была прекращена и он был удален из Chrome, Google совместно с разработчиками популярной базы данных SQLite предложила замену Web SQL на основе SQLite. Подробнее о том, как использовать SQLite Wasm в браузере с поддержкой частной файловой системы источника, читайте в этой статье.
Заключение
Прошли те времена, когда хранилище было ограничено и пользователю приходилось хранить все больше и больше данных. Сайты могут хранить практически все ресурсы и данные, необходимые для работы. С помощью StorageManager API можно определить, сколько места доступно и сколько уже занято. А с помощью постоянного хранилища вы можете защитить данные от удаления, если только пользователь сам не удалит их.
Дополнительные ресурсы
Спасибо!
Особая благодарность Джарриду Гудману (Jarryd Goodman), Филу Уолтону (Phil Walton), Эйдзи Китамуре (Eiji Kitamura), Дэниелу Мерфи (Daniel Murphy), Дарвину Хуангу (Darwin Huang), Джошу Беллу (Josh Bell), Марейну Крейсселбринку (Marijn Kruisselbrink) и Виктору Костану (Victor Costan) за рецензирование этого руководства. Благодарим Эйдзи Китамуру, Эдди Османи и Марка Коэна, которые написали оригинальные статьи, на которых основана эта статья. Эйдзи написал полезный инструмент под названием Browser Storage Abuser, который помог проверить текущее поведение. Оно позволяет хранить как можно больше данных и видеть ограничения на хранение в вашем браузере. Спасибо Франсуа Бофору, который покопался в Safari, чтобы выяснить ограничения на хранение, и Томасу Штайнеру за добавление информации о частной файловой системе источника, сегментах хранилища, SQLite Wasm и общем обновлении контента в 2024 году.