估算可用儲存空間

傑夫波斯尼克
Jeff Posnick

tl;dr

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 和一些圖片組成。Service WorkerCache Storage API 提供儲存這些核心資源所需的基礎架構,稍後用於快速載入網頁應用程式,理想情況下,應該完全略過網路。(與網頁應用程式建構程序整合的工具 (例如新的 Workbox 程式庫或舊版 sw-precache),可以完全自動處理儲存、更新及使用這類資料的程序。)

但其他類型的資料呢?這些資源在載入網頁應用程式時並非必要,但對於整體使用者體驗來說至關重要。舉例來說,當您編寫圖片編輯網頁應用程式時,您可能會想儲存一或多個映像檔的本機副本,讓使用者可在修訂版本之間切換並復原工作。如果您正在開發離線媒體播放體驗,將音訊或影片檔案儲存在本機會成為重要功能。凡是可個人化的網頁應用程式,最終都需要儲存某些狀態資訊。如何得知這類執行階段儲存空間還有多少可用空間?儲存空間用盡時會發生什麼情況?

過去時間:window.webkitStorageInfonavigator.webkitTemporaryStorage

瀏覽器過去曾透過前置字串等前置字串支援這類自我檢查,例如非常舊 (和已淘汰) 的 window.webkitStorageInfo,以及不具等效的 navigator.webkitTemporaryStorage,但仍屬於非標準 navigator.webkitTemporaryStorage。雖然這些介面提供了實用資訊,但與網路標準無關。

這時 WHATWG Storage Standard 就會在這裡輸入圖片。

未來趨勢:navigator.storage

為持續瞭解儲存空間存取標準,一些實用的 API 已提供了一些實用的 API 供 StorageManager 介面使用,該介面會以 navigator.storage 的形式向瀏覽器顯示。和許多其他較新的網路 API 一樣,navigator.storage 「僅可在安全」(透過 HTTPS 或 localhost 提供) 來源上使用。

我們在去年推出 navigator.storage.persist() 方法,可讓網頁應用程式要求儲存空間不受自動清理功能的影響。

現已透過 navigator.storage.estimate() 方法加入,該方法可做為 navigator.webkitTemporaryStorage.queryUsageAndQuota() 的新型替代項目。estimate() 會傳回類似資訊,但會公開以 promise 為基礎的介面,而此介面與其他新型非同步 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;
  }
}

預估值的準確度有多高?

您從函式回傳的資料並不容易,因為這只是來源所使用空間的估計值。它就在函式名稱中!usagequota 值都無法保持穩定,因此建議您考量下列事項:

  • usage 會反映特定來源在使用相同來源資料時能有效使用多少位元組,進而可能受到內部壓縮技術、可能包含未使用空間的固定大小配置區塊,以及刪除後暫時建立的「空值標記」記錄所影響。為避免精確大小資訊外洩,本機儲存的跨來源不透明資源可能會為整體 usage 值產生額外的邊框間距位元組。
  • quota 代表目前為來源保留的空間大小。這個值取決於一些常數因素,例如整體儲存空間大小,以及一些潛在的波動因素,包括目前未使用的儲存空間量。因此,就像裝置上的其他應用程式寫入或刪除資料一樣,瀏覽器願意投入網頁應用程式來源的空間量可能會改變。

簡報內容:功能偵測和備用

從 Chrome 61 開始,estimate() 預設為啟用。Firefox 目前正在測試 navigator.storage,但自 2017 年 8 月起,這項功能預設為停用。您必須啟用 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});
}