Доброго времени суток, друзья.

Представляю вашему вниманию перевод статьи «Storage for the Web» автора Pete LePage.

Существует несколько технологий для хранения данных в браузере. Какая из них лучше?

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

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


Могу посоветовать следующее:


IndexedDB и Cache API поддерживаются всеми современными браузерами. Они являются асинхронными, т.е. не блокируют основной поток (выполнения кода). Они доступны в объекте Window, веб-воркерах и сервис-воркерах. Другими словами, их можно использовать везде.

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


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

  • SessionStorage ограничено сессией браузера. Оно может использоваться для хранения незначительной информации, характерной для сессии, например, ключа IndexedDB. Его следует использовать с осторожностью, поскольку оно является синхронным, блокирующим основной поток. Оно ограничено примерно 5 Мб памяти (зависит от браузера), может содержать только строковые данные и недоступно в веб-воркерах и сервис-воркерах.
  • Использования LocalStorage также следует избегать по причине его синхронности. Оно ограничено примерно 5 Мб (зависит от браузера), может содержать только строковые данные и недоступно в веб-воркерах и сервис-воркерах.
  • Cookies не должны использоваться для хранения данных. Они отправляются на сервер с каждым HTTP-запросом, поэтому хранение в них больших объемов данных может привести к существенному снижению производительности за счет увеличения продолжительности запросов. Они являются синхронными, недоступны в веб-воркерах, могут хранить только строковые данные.
  • File System API и FileWriter API предоставляют методы для чтения и записи файлов в изолированную файловую систему. Несмотря на то, что данная технология является асинхронной, использовать ее не рекомендуется, поскольку она поддерживается только Chrome.
  • Native File System API спроектировано для облегчения чтения и записи файлов в локальную файловую систему пользователя. Пользователь должен предоставить разрешение на работу с его локальными файлами. Такое разрешение ограничено сессией браузера.
  • WebSQL не должно использоваться, а существующие решения должны перейти на IndexedDB. Поддержка отсутствует почти во всех браузерах. W3C перестал разрабатывать соответствующую спецификацию в 2010 г.
  • Application Cache не должно использоваться, а существующие решения должны перейти на сервис-воркеры и Cache API. Данная технология признана устаревшей.

Какой объем данных я могу хранить?


Как минимум, несколько сотен мегабайт, потенциально, сотни гигабайт. Это зависит от браузера, однако объем хранилища, как правило, зиждется на объеме доступной памяти в устройстве пользователя.

  • Chrome позволяет использовать до 60% дискового пространства. Вы можете использовать StorageManager API для определения лимита (квоты).
  • Internet Explorer 10 и более поздние версии может хранить до 250 Мб данных.
  • Firefox позволяет хранить до 2 Гб данных. Вы можете использовать StorageManager API для определения лимита.
  • Safari (как десктопный, так и мобильный) позволяет хранить до 1 Гб данных. При достижении лимита, Safari обращается к пользователю за разрешением увеличить квоту на 200 Мб.

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

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

Как проверить остаток лимита?


Для этого во многих браузерах можно воспользоваться StorageManager 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 API пока поддерживается не всеми браузерами. Однако даже если он поддерживается, необходимо предусматривать обработчик ошибок. В некоторых случаях квота может превышать реальный объем хранилища.

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


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



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

Как обрабатывать ошибки?


Что делать при достижении лимита? Разумеется, обрабатывать ошибки, будь то QuotaExceededError или нечто иное. Затем, в зависимости от дизайна вашего приложения, следует выбрать способ их обработки. Можно, например, удалять старый контент или данные в зависимости от их размера, либо предоставлять пользователю возможность решать, что удалять.

IndexedDB и Cache API выбрасывают DOMError QuotaExceededError при превышении квоты.

IndexedDB


При достижении лимита попытка записать данные в IndexedDB потерпит неудачу. Будет вызван метод onabort() с событием в качестве аргумента. Событие будет содержать DOMException в свойстве ошибки. Проверка имени ошибки вернет 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 при достижении лимита будет отклонена с QuotaExceededError DOMException.

try {
    const cache = await caches.open('my-cache')
    await cache.add(new Request('/sample1.jpg'))
} catch (error) {
    if(error.name = 'QuotaExceededError') {
        // ...
    }
}

Как работает очистка хранилища?


Веб-хранилища подразделяются на две категории, «автономные» и «управляемые». Автономное означает, что хранилище может быть очищено браузером без участия пользователя, однако оно менее устройчиво при длительном использовании, а также при наличии критических данных. Управляемые хранилища не очищаются автоматически при заполнении. Пользователь должен вручную очищать такие хранилища (через настройки браузера).

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

Условия для очистки хранилища следующие:

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

Начиная с iOS и iPad 13.4, Safari 13.1 на macOS, существует семидневный срок хранения данных. Это означает, что если пользователь в течение семи дней не обращается к данным, они будут удалены. Данная политика не применяется в отношении приложений, добавленных на главный экран.

Бонус: обертка из промисов над IndexedDB


IndexedDB — это низкоуровневое API, предполагающее определенную настройку перед использованием, которая может быть лишней при необходимости хранения простых данных. В отличие от большинства современных API, основанных на промисах, она основано на событиях. Обертка из промисов, такая как idb, скрывает некоторые мощные возможности данного хранилища, но, что более важно, она также скрывает его сложные внутренние механизмы (транзакции, версионирование).

Заключение


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

Прим. пер.:

  • Здесь можно посмотреть, как написать приложение для заметок с использованием idb.
  • Здесь можно посмотреть, как работают сервис-воркеры.

Благодарю за потраченное время. Надеюсь, оно было потрачено не зря.