Интернет-соединения могут быть нестабильными или вообще отсутствовать, поэтому офлайн-поддержка и надежная работа являются стандартными функциями прогрессивных веб-приложений PWA. Даже в идеальных беспроводных средах разумное использование кэширования и других методов хранения данных может существенно улучшить взаимодействие с пользователем. Существует несколько способов кэширования ваших статических ресурсов (HTML-файлов, JavaScript, CSS, изображений и т.д.) и данных (пользовательские данные, новостные статьи и т.д.). Но какой из них лучше? И какое количество данных можно хранить? А как предотвратить их замещение более новыми?

Что использовать?

Вот общая рекомендация по хранению ресурсов:

  • для сетевых ресурсов, необходимых для загрузки вашего приложения и файлосодержащего контента, используйте кэш-память Cache Storage API (составляющая сервис-воркеров);
  • для других данных используйте IndexedDB (с обёрткой промисов).

IndexedDB и Cache Storage API поддерживаются всеми современными браузерами. Они оба асинхронны и не блокируют основной поток. Они доступны из объекта window, веб-воркеров и сервис-воркеров, что упрощает их использование в любом месте кода.

Как насчет других механизмов хранения?

В браузере доступно несколько других механизмов хранения, но они имеют ограниченное использование и могут вызвать серьезные проблемы с производительностью.

SessionStorage - сессионное хранилище - зависит от вкладки браузера и ограничено временем существования вкладки. Это может быть полезно для хранения небольших объемов информации о сеансе, например ключа IndexedDB. Его следует использовать с осторожностью, поскольку оно синхронное и блокирует основной поток. Ограничение примерно 5 МБ и может содержать только строки. Поскольку оно зависит от вкладки, хранилище недоступно для веб-воркеров или сервис-воркеров.

LocalStorage - локальное хранилище - следует избегать его использования, поскольку оно синхронное и блокирует основной поток. Ограничение примерно 5 МБ и может содержать только строки. LocalStorage недоступно для веб-воркеров или сервис-воркеров.

Cookies - файлы куки - имеют свое применение, но не должны использоваться в качестве хранилища. Файлы cookie отправляются с каждым HTTP-запросом, поэтому хранение чего-либо, кроме небольшого количества данных, значительно увеличит размер каждого веб-запроса. Они синхронны и недоступны для веб-воркеров. Как LocalStorage и SessionStorage, файлы cookie ограничены только строками.

File Syslem API - API файловой системы и FileWriter API предоставляют методы для чтения и записи файлов в изолированную файловую систему. Хоть это и асинхронно, использовать не рекомендуется, потому что это доступно только в браузерах на основе Chromium.

File Syslem Access API - API доступа к файловой системе - был разработан, чтобы упростить пользователям чтение и редактирование файлов в их локальной файловой системе. Пользователь должен предоставить разрешение, прежде чем страница сможет прочесть или записать данные в любой локальный файл, а разрешения не сохраняются между сессиями.

WebSQL - не следует использовать, а существующее использование следует перенести на IndexedDB. Поддержка была удалена почти из всех основных браузеров. W3C прекратил поддержку спецификации Web SQL в 2010 году, и дальнейших обновлений не планируется.

Application Cache - кэш-приложения не следует использовать, а существующее использование следует перенести на сервис-воркеры и Cache API. Они устарели, и в будущем поддержка их в браузерах будет прекращена.

Какое количество данных можно хранить?

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

  • Chrome позволяет браузеру использовать до 80% всего дискового пространства. Источник может использовать до 60% от общего дискового пространства. Вы можете использовать StorageManager API, чтобы определить максимально доступный размер. Другие браузеры на основе Chromium могут позволить браузеру использовать больше памяти. Для получения подробной информации о реализации Chrome, можно изучить реквест.
  • Internet Explorer 10 и более поздние версии могут хранить до 250 МБ и когда будет использовано более 10 МБ, сообщит пользователю.
  • Firefox позволяет браузеру использовать до 50% свободного дискового пространства. Группа eTLD+1 (например, example.com, www.example.com и foo.bar.example.com) может использовать до 2 ГБ. Также вы можете использовать StorageManager API, чтобы определить, сколько места еще доступно.
  • Safari (как для десктопа, так и для мобильных устройств) позволяет использовать около 1 ГБ. Когда предел будет достигнут, Safari предложит пользователю увеличить лимит с шагом 200 МБ. Однако официальной документации по этому поводу нет.

В прошлом, если сайт превышал определенный порог сохраненных данных, браузер предлагал пользователю предоставить разрешение на использование большего количества данных. Например, если источник использует более 50 МБ, браузер предложит пользователю разрешить ему хранить до 100 МБ, а затем запросит снова с шагом 50 МБ.

Сегодня большинство современных браузеров ничего не запрашивают у пользователя и позволяют сайту использовать отведенный ему лимит. Исключением является Safari, который требует 750 МБ, запрашивая разрешение на хранение до 1,1 ГБ. Если источник пытается использовать больше выделенного, дальнейшие попытки записи данных завершатся ошибкой.

Как проверить доступный объем хранилища?

Во многих браузерах вы можете использовать StorageManager API для определения объема хранилища, доступного источнику, и того, какой объем хранилища он использует. API сообщает общее количество байтов, используемых IndexedDB и Cache API, и позволяет рассчитать приблизительное оставшееся доступное пространство для хранения данных.

if (navigator.storage && navigator.storage.estimate) {
  const quota = await navigator.storage.estimate();
  //  quota.usage - количество используемых байт
  //  quota.quota - максимально доступное количество байт
  const percentageUsed = (quota.usage / quota.quota) * 100;
  console.log(`Вы используете ${percentageUsed}% доступного хранилища`);
  const remaining = quota.quota - quota.usage;
  console.log(`У Вас осталось ${remaining} байт`);
}

StorageManager пока реализован не во всех браузерах, поэтому необходимо сперва определить его доступность перед использованием. Однако даже если он доступен, отлавливать ошибки превышения нормы всё равно необходимо. В некоторых случаях доступный предел может превышать фактический объем доступного хранилища.

Инспектирование

Во время разработки для отслеживания типов хранилищ можно использовать инструменты разработчика браузера, чтобы проверить их состояние или очистить все сохраненные данные.

В Chrome 88 была добавлена новая функция, которая позволяет переопределить объёмы хранилища сайта на специальной панели. Эта функция дает вам возможность имитировать различные устройства и тестировать поведение ваших приложений в сценариях с малой памятью. В chrome зайдите в панель разработчика devTools (F12), перейдите на вкладку "Application" и слева выберите "Storage", установите флажок на "Simulate custom storage quote" и введите любое допустимое число, чтобы смоделировать объём хранилища.

С помощью простенькой тулзы можете поэкспериментировать с различными механизмами хранения данных и посмотреть, что произойдет, когда вы используете весь свой лимит.

Как справиться с превышением лимита?

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

И IndexedDB, и Cache API генерируют ошибку DOMError с именем QuotaExceededError, когда вы превышаете доступный лимит.

IndexedDB

Если лимит превышен, попытки записи в IndexedDB завершатся ошибкой. Будет вызван обработчик транзакции onabort(), передающий событие. Событие будет содержать исключение DOMException в свойстве ошибки. Проверка name вернет QuotaExceededError.

const transaction = idb.transaction(['entries'], 'readwrite');
transaction.onabort = function(event) {
  const error = event.target.error;  // DOMException
  if (error.name == 'QuotaExceededError') {
    // код для фолбэка
  }
};

Cache API

При превышении лимита, попытки записи в Cache API будут отклонены с DOMException QuotaExceededError.

try {
  const cache = await caches.open('my-cache');
  await cache.add(new Request('/sample1.jpg'));
} catch (err) {
  if (error.name === 'QuotaExceededError') {
    // код фолбэка
  }
}

Как работает замещение?

Веб-хранилище подразделяется на две категории: "лучшего действия" и "постоянное". "Лучшее действие" означает, что хранилище может быть очищено браузером, не отвлекая пользователя; однако это менее надежно для долгосрочных или важных данных. "Постоянное" хранилище не очищается автоматически, когда мало места. Пользователю необходимо вручную очистить это хранилище (в настройках браузера).

По умолчанию, данные сайта (включая IndexedDB, Cache API и т.д.) попадают в категорию "лучшего действия", что означает - если сайт не запросил "постоянное" хранилище, браузер может удалить данные сайта по своему усмотрению, например, когда на устройстве мало памяти.

Политика замещения для категории "лучшего действия" такова:

  • Браузеры на основе Chromium начинают замещать данные, когда в браузере заканчивается свободное место, сначала удаляя все данные сайта из наименее используемого источника, а затем из следующего, пока браузер не перестанет превышать лимит.
  • Internet Explorer 10+ не вытесняет данные, но предотвращает дальнейшую запись.
  • Firefox начнет замещать данные, когда доступное дисковое пространство будет заполнено, сначала удаляя все данные сайта из наименее используемого источника, а затем из следующего, пока браузер не перестанет превышать лимит.
  • Ранее Safari не удалял данные, но недавно установил новое семидневное ограничение для всего доступного для записи хранилища (см. ниже).

Начиная с iOS и iPadOS 13.4 и Safari 13.1 на macOS, существует семидневное ограничение на все хранилища сценариев с возможностью записи, включая IndexedDB, регистрацию сервис-воркера и Cache API. Это означает, что Safari удалит весь контент из кэша после семи дней использования Safari, если пользователь не будет взаимодействовать с сайтом. Эта политика замещения не применяется к установленным PWA, добавленным на главный экран.

Заключение

Прошли те времена, когда хранилище было ограничено, и пользователю предлагалось хранить все больше и больше данных. Сайты могут эффективно хранить все ресурсы и данные, необходимые для работы. Используя StorageManager API, вы можете определить, сколько вам доступно и сколько вы использовали. А с постоянным хранилищем, если пользователь не удалит его, вы можете защитить его от замещения.