Verfügbaren Speicherplatz schätzen

tl;dr

Chrome 61, weitere Browser folgen, zeigt jetzt eine Schätzung an, wie viel Speicherplatz eine Webanwendung verwendet und wie viel verfügbar ist über:

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

Moderne Web-Apps und Datenspeicher

Wenn Sie an die Speicheranforderungen einer modernen Webanwendung denken, ist es hilfreich, das gespeicherte in zwei Kategorien zu unterteilen: die Kerndaten, die zum Laden der Webanwendung benötigt werden, und die Daten, die nach dem Laden der Anwendung für eine sinnvolle Nutzerinteraktion erforderlich sind.

Der erste Datentyp, der zum Laden der Webanwendung benötigt wird, besteht aus HTML, JavaScript, CSS und eventuell einigen Bildern. Service Worker stellen zusammen mit der Cache Storage API die erforderliche Infrastruktur zum Speichern dieser Kernressourcen bereit. Diese können sie später verwenden, um Ihre Webanwendung schnell zu laden und damit das Netzwerk vollständig zu umgehen. Mit Tools, die in den Build-Prozess Ihrer Webanwendung eingebunden werden, wie die neuen Workbox-Bibliotheken oder die ältere sw-precache, können Sie das Speichern, Aktualisieren und Verwenden dieser Art von Daten vollständig automatisieren.

Aber was ist mit dem anderen Datentyp? Dies sind Ressourcen, die zum Laden Ihrer Webanwendung nicht benötigt werden, aber für die Nutzerfreundlichkeit insgesamt eine entscheidende Rolle spielen können. Wenn Sie beispielsweise eine Webanwendung zur Bildbearbeitung schreiben, möchten Sie vielleicht eine oder mehrere lokale Kopien eines Bildes speichern, damit Nutzer zwischen Überarbeitungen wechseln und ihre Arbeit rückgängig machen können. Wenn Sie eine Offline-Medienwiedergabe entwickeln, wäre das lokale Speichern von Audio- oder Videodateien eine wichtige Funktion. Jede Webanwendung, die personalisiert werden kann, muss bestimmte Statusinformationen speichern. Woher wissen Sie, wie viel Speicherplatz für diese Art von Laufzeitspeicher verfügbar ist und was passiert, wenn kein Platz mehr vorhanden ist?

Vergangenheit: window.webkitStorageInfo und navigator.webkitTemporaryStorage

Browser haben diese Art der Selbstprüfung in der Vergangenheit über Oberflächen mit Präfix unterstützt, z. B. über das sehr alte (und verworfene) window.webkitStorageInfo und das noch nicht ganz standardmäßige navigator.webkitTemporaryStorage. Diese Schnittstellen haben zwar nützliche Informationen geliefert, sind aber keine Zukunft als Webstandards.

Hier kommt der WHATWG Storage Standard ins Spiel.

Die Zukunft: navigator.storage

Im Rahmen der laufenden Arbeit am Storage Living Standard haben wir einige nützliche APIs in die Schnittstelle StorageManager aufgenommen, die für Browser als navigator.storage angezeigt wird. Wie viele andere neuere Web-APIs ist navigator.storage nur für sichere Quellen verfügbar, die über HTTPS oder localhost bereitgestellt werden.

Letztes Jahr haben wir die Methode navigator.storage.persist() eingeführt, mit der Ihre Webanwendung anfordern kann, dass ihr Speicher von der automatischen Bereinigung ausgenommen wird.

Es wird jetzt durch die Methode navigator.storage.estimate() verbunden, die als moderner Ersatz für navigator.webkitTemporaryStorage.queryUsageAndQuota() dient. estimate() gibt ähnliche Informationen zurück, stellt jedoch eine Promise-basierte Schnittstelle bereit, die anderen modernen asynchronen APIs entspricht. Das Promise, das estimate() zurückgibt, wird mit einem Objekt aufgelöst, das zwei Attribute enthält: usage für die Anzahl der aktuell verwendeten Byte und quota für die maximale Anzahl von Byte, die vom aktuellen Ursprung gespeichert werden können. Wie alles andere in Bezug auf den Speicher wird das Kontingent auf den gesamten Ursprung angewendet.

Wenn eine Webanwendung versucht, Daten zu speichern, z. B. mit IndexedDB oder der Cache Storage API, die groß genug sind, um das verfügbare Kontingent für einen bestimmten Ursprung zu überschreiten, schlägt die Anfrage mit der Ausnahme QuotaExceededError fehl.

Speicherplatzschätzungen in der Praxis

Wie Sie estimate() genau verwenden, hängt vom Typ der Daten ab, die Ihre Anwendung speichern muss. Sie können beispielsweise ein Steuerelement in Ihrer Benutzeroberfläche aktualisieren, damit die Nutzer nach Abschluss eines Speichervorgangs wissen, wie viel Speicherplatz verwendet wird. Idealerweise stellen Sie dann eine Schnittstelle bereit, über die Nutzer nicht mehr benötigte Daten manuell bereinigen können. Sie können Code folgendermaßen schreiben:

// 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;
  }
}

Wie genau ist die Schätzung?

Es ist kaum zu übersehen, dass die Daten, die Sie von der Funktion erhalten, nur eine Schätzung des Raums sind, den ein Ursprung verwendet. Sie befindet sich direkt im Funktionsnamen! Weder die Werte für usage noch die Werte für quota sind dafür vorgesehen, stabil zu sein. Daher wird Folgendes empfohlen:

  • usage gibt an, wie viele Byte ein bestimmter Ursprung effektiv für Daten mit gleichem Ursprung verwendet, was wiederum durch interne Komprimierungstechniken, Zuweisungsblöcke mit fester Größe, die ungenutzten Speicherplatz umfassen können, und das Vorhandensein von Tombstone-Datensätzen, die nach einem Löschen vorübergehend erstellt werden können, beeinträchtigt werden kann. Damit keine genauen Größeninformationen verloren gehen, können ursprungsübergreifende, undurchsichtige Ressourcen, die lokal gespeichert sind, zusätzliche Padding-Byte zum Gesamtwert von usage beitragen.
  • quota gibt an, wie viel Platz derzeit für einen Startort reserviert ist. Der Wert hängt von einigen konstanten Faktoren wie der Gesamtspeichergröße, aber auch von einer Reihe potenziell flüchtiger Faktoren ab, einschließlich der Menge an derzeit nicht genutztem Speicherplatz. Wenn also andere Anwendungen auf einem Gerät Daten schreiben oder löschen, ändert sich wahrscheinlich der Speicherplatz, den der Browser für den Ursprung Ihrer Web-App zur Verfügung stellt.

Jetzt neu: Funktionserkennung und Fallbacks

estimate() ist ab Chrome 61 standardmäßig aktiviert. navigator.storage testet Firefox gerade. Seit August 2017 ist die Funktion jedoch nicht mehr standardmäßig aktiviert. Sie müssen die Einstellung dom.storageManager.enabled aktivieren, um sie zu testen.

Wenn Sie mit Funktionen arbeiten, die noch nicht in allen Browsern unterstützt werden, ist die Funktionserkennung ein Muss. Sie können die Feature-Erkennung mit einem vielversprechenden Wrapper zusätzlich zu den älteren navigator.webkitTemporaryStorage-Methoden kombinieren, um eine konsistente Oberfläche gemäß folgender Aspekte zu erhalten:

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});
}