Verfügbaren Speicherplatz schätzen

Jan Posnick
Jeff Posnick

tl;dr

In Chrome 61 und weiteren Browsern werden ab sofort Schätzungen dazu angezeigt, wie viel Speicherplatz eine Webanwendung verwendet und wie viel verfügbar ist:

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 Datenspeicherung

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

Der erste Datentyp, der zum Laden Ihrer Webanwendung erforderlich ist, besteht aus HTML, JavaScript, CSS und möglicherweise einigen Bildern. Service Worker stellen zusammen mit der Cache Storage API die erforderliche Infrastruktur bereit, um diese Kernressourcen zu speichern und sie später zum schnellen Laden Ihrer Webanwendung zu verwenden. Idealerweise umgehen Sie das Netzwerk vollständig. Mit Tools, die in den Build-Prozess Ihrer Webanwendung eingebunden sind, wie die neuen Workbox-Bibliotheken oder die älteren 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 allgemeine Nutzerfreundlichkeit eine entscheidende Rolle spielen können. Wenn Sie beispielsweise eine Webanwendung zur Bildbearbeitung schreiben, möchten Sie möglicherweise eine oder mehrere lokale Kopien eines Bildes speichern, damit Nutzer zwischen Versionen wechseln und ihre Arbeit rückgängig machen können. Wenn Sie eine Offlinemedienwiedergabe entwickeln, wäre das lokale Speichern von Audio- oder Videodateien ein wichtiges Feature. Jede Web-App, 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 Ihnen der Platz ausgeht?

In der Vergangenheit: window.webkitStorageInfo und navigator.webkitTemporaryStorage

In der Vergangenheit haben Browser diese Art der Selbstprüfung über Schnittstellen mit Präfixen unterstützt, z. B. den sehr alten (und veralteten) window.webkitStorageInfo und den nicht ganz neuen, aber noch nicht standardmäßigen navigator.webkitTemporaryStorage. Diese Oberflächen lieferten zwar nützliche Informationen, haben aber keine Zukunft als Webstandards.

Hier kommt der whatWG-Speicherstandard ins Spiel.

Die Zukunft: navigator.storage

Im Rahmen der fortlaufenden Arbeit am Storage Living Standard wurde die Schnittstelle StorageManager mit einigen nützlichen APIs verknüpft, die für Browser als navigator.storage verfügbar ist. Wie viele andere neuere Web-APIs ist navigator.storage nur bei sicheren Ursprüngen 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.

Jetzt wird sie durch die Methode navigator.storage.estimate() verbunden, die als moderner Ersatz für navigator.webkitTemporaryStorage.queryUsageAndQuota() dient. estimate() gibt ähnliche Informationen zurück, stellt aber eine Promise-basierte Schnittstelle bereit, die mit anderen modernen asynchronen APIs vergleichbar ist. 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 maximalen Byte, die vom aktuellen Ursprung gespeichert werden können. Wie bei allem, was mit dem Speicher zu tun hat, wird das Kontingent auf den gesamten Ursprung angewendet.

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

Speicherschätzungen in der Praxis

Wie Sie estimate() genau verwenden, hängt von der Art der Daten ab, die Ihre Anwendung speichern muss. Beispielsweise könnten Sie ein Steuerelement auf der Benutzeroberfläche aktualisieren, das Nutzer darüber informiert, wie viel Speicherplatz nach jedem Speichervorgang verwendet wird. Idealerweise stellen Sie dann eine Schnittstelle zur Verfügung, ü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 von einem Ursprung belegten Raums sind. Sie steht direkt im Funktionsnamen. Weder die Werte usage noch die Werte für quota sind darauf ausgelegt, stabil zu sein. Daher sollte Folgendes berücksichtigt werden:

  • usage gibt an, wie viele Byte ein bestimmter Ursprung effektiv für Daten mit demselben Ursprung nutzt, was wiederum von internen Komprimierungstechniken, Zuweisungsblöcken fester Größe, die ungenutzten Speicherplatz enthalten können, und dem Vorhandensein von Tombstone-Einträgen, die nach einem Löschen vorübergehend erstellt werden können, beeinflusst werden. Damit keine exakten Größeninformationen verloren gehen, können ursprungsübergreifende, lokal gespeicherte opake Ressourcen zusätzliche Padding-Byte zum Gesamtwert usage beitragen.
  • quota gibt den Speicherplatz an, der derzeit für einen Ursprung reserviert ist. Der Wert hängt von einigen konstanten Faktoren wie der Gesamtspeichergröße, aber auch von einer Reihe potenziell volatiler Faktoren ab, einschließlich des aktuell nicht genutzten Speicherplatzes. Wenn also andere Anwendungen auf einem Gerät Daten schreiben oder löschen, wird sich wahrscheinlich der Speicherplatz ändern, den der Browser dem Ursprung Ihrer Webanwendung zuweisen möchte.

Gegenwart: Funktionserkennung und Fallbacks

estimate() ist ab Chrome 61 standardmäßig aktiviert. In Firefox wird navigator.storage getestet, aber seit August 2017 ist die Funktion nicht standardmäßig aktiviert. Sie müssen die Einstellung dom.storageManager.enabled aktivieren, um sie zu testen.

Wenn Sie mit Funktionen arbeiten, die noch nicht von allen Browsern unterstützt werden, ist die Funktionserkennung ein Muss. Sie können die Featureerkennung mit einem Promise-basierten Wrapper zusätzlich zu den älteren navigator.webkitTemporaryStorage-Methoden kombinieren, um eine konsistente Schnittstelle in folgender Hinsicht bereitzustellen:

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