Оценка доступного пространства для хранения

Джефф Посник
Jeff Posnick

вкратце; доктор

Chrome 61, за которым последуют другие браузеры, теперь предоставляет оценку того, сколько памяти использует веб-приложение и сколько доступно через:

if ('storage' in navigator && 'estimate' in navigator.storage) {
  navigator.storage.estimate().then(({usage, quota}) => {
    console.log(`Using ${usage} out of ${quota} bytes.`);
  });
}

Современные веб-приложения и хранилище данных

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

Первый тип данных, необходимый для загрузки вашего веб-приложения, состоит из HTML, JavaScript, CSS и, возможно, некоторых изображений. Сервис-воркеры вместе с Cache Storage API предоставляют необходимую инфраструктуру для сохранения этих основных ресурсов и последующего использования их для бы��трой загрузки вашего веб-приложения, в идеале полностью минуя сеть. (Инструменты, которые интегрируются с процессом сборки вашего веб-приложения, такие как новые библиотеки Workbox или более старая версия sw-precache , могут полностью автоматизировать процесс хранения, обновления и использования данных этого типа.)

А как насчет другого типа данных? Это ресурсы, которые не нужны для загрузки вашего веб-приложения, но могут сыграть решающую роль в общем взаимодействии с пользователем. Например, если вы пишете веб-приложение для редактирования изображений, вам может потребоваться сохранить одну или несколько локальных копий изображения, чтобы пользователи могли переключаться между редакциями и отменять свою работу. Или, если вы разрабатываете автономное воспроизведение мультимедиа, сохранение аудио- или видеофайлов локально будет критически важной функцией. Каждое веб-приложение, которое можно персонализировать, в конечном итоге должно сохранять некоторую информацию о состоянии . Как узнать, сколько места доступно для такого типа хранилища среды выполнения и что произойдет, когда место закончится?

Прошлое: window.webkitStorageInfo и navigator.webkitTemporaryStorage

Браузеры исторически поддерживали этот тип самоанализа через префиксные интерфейсы, такие как очень старый (и устаревший) window.webkitStorageInfo и не совсем старый, но все еще нестандартный navigator.webkitTemporaryStorage . Хотя эти интерфейсы предоставляют полезную информацию, у них нет будущего в качестве веб-стандартов.

Именно здесь на сцену выходит стандарт хранения данных WHATWG .

Будущее: navigator.storage

В рамках продолжающейся работы над Storage Living Standard несколько полезных API были добавлены в интерфейс StorageManager , который доступен браузерам как navigator.storage . Как и многие другие новые веб-API, navigator.storage доступен только в защищенных источниках (обслуживаемых через HTTPS или локальный хост).

В прошлом году мы представили метод navigator.storage.persist() , который позволяет вашему веб-приложению запрашивать освобождение его хранилища от автоматической очистки.

Теперь к нему присоединился метод navigator.storage.estimate() , который служит современной заменой navigator.webkitTemporaryStorage.queryUsageAndQuota() . estimate() возвращает аналогичную информацию, но предоставляет интерфейс на основе обещаний , который соответствует другим современным асинхронным API. Обещание, которое возвращает estimate() , разрешается с помощью объекта, содержащего два свойства: usage , представляющее количество используемых в данный момент байтов, и quota , представляющее максимальное количество байтов, которые могут быть сохранены текущим источником . (Как и все остальное, что связано с хранилищем, квота применяется ко всему источнику.)

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

Оценки объема хранилища в действии

То, как вы используете estimate() зависит от типа данных, которые ваше приложение должно хранить. Например, вы можете обновить элемент управления в своем интерфейсе, чтобы пользо��атели знали, сколько места используется после завершения каждой операции хранения. В идеале вам следуе�� ��редоставить интерфейс, позволяющий пользователям вручную очищать данные, которые больше не нужны. Вы можете написать код примерно так:

// For a primer on async/await, see
// https://developers.google.com/web/fundamentals/getting-started/primers/async-functions
async function storeDataAndUpdateUI(dataUrl) {
  // Pro-tip: The Cache Storage API is available outside of service workers!
  // See https://googlechrome.github.io/samples/service-worker/window-caches/
  const cache = await caches.open('data-cache');
  await cache.add(dataUrl);

  if ('storage' in navigator && 'estimate' in navigator.storage) {
    const {usage, quota} = await navigator.storage.estimate();
    const percentUsed = Math.round(usage / quota * 100);
    const usageInMib = Math.round(usage / (1024 * 1024));
    const quotaInMib = Math.round(quota / (1024 * 1024));

    const details = `${usageInMib} out of ${quotaInMib} MiB used (${percentUsed}%)`;

    // This assumes there's a <span id="storageEstimate"> or similar on the page.
    document.querySelector('#storageEstimate').innerText = details;
  }
}

Насколько точна оценка?

Трудно не заметить тот факт, что данные, которые вы получаете от функции, — это всего лишь оценка пространства, которое использует источник. Это прямо в названии функции! Ни usage , ни значения quota не должны быть стабильными, поэтому рекомендуется принять во внимание следующее:

  • usage отражает, сколько байтов данный источник эффективно использует для данных одного и того же происхождения , на что, в свою очеред��, могут влиять методы внутреннего сжатия, блоки распределения фиксированного размера, которые могут включать неиспользуемое пространство, а также наличие записей «надгробия» , которые могут быть созданный временно после удаления. Чтобы предотвратить утечку точной информации о размере, непрозрачные ресурсы перекрестного происхождения, сохраненные локально, могут добавлять дополнительные байты заполнения к общему значению usage .
  • quota отражает объем пространства, зарезервированного в настоящее время для источника. Значение зависит от некоторых постоянных факторов, таких как общий размер хранилища, а также от ряда потенциально изменчивых факторов, включая объем неиспользуемого в настоящее время дискового пространства. Поскольку другие приложения на устройстве записывают или удаляют данные, объем места, которое браузер готов выделить для источника вашего веб-приложения, скорее всего, изменится.

Настоящее: обнаружение функций и резервные варианты

estimate() включен по умолчанию, начиная с Chrome 61. Firefox экспериментирует с navigator.storage , но по состоянию на август 2017 года он не включен по умолчанию. Вам необходимо включить настройку dom.storageManager.enabled , чтобы протестировать ее.

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

function storageEstimateWrapper() {
  if ('storage' in navigator && 'estimate' in navigator.storage) {
    // We've got the real thing! Return its response.
    return navigator.storage.estimate();
  }

  if ('webkitTemporaryStorage' in navigator &&
      'queryUsageAndQuota' in navigator.webkitTemporaryStorage) {
    // Return a promise-based wrapper that will follow the expected interface.
    return new Promise(function(resolve, reject) {
      navigator.webkitTemporaryStorage.queryUsageAndQuota(
        function(usage, quota) {resolve({usage: usage, quota: quota})},
        reject
      );
    });
  }

  // If we can't estimate the values, return a Promise that resolves with NaN.
  return Promise.resolve({usage: NaN, quota: NaN});
}