# StorageManager.estimate()

> navigator.storage.estimate() 返回什么、Chrome、Firefox 与 Safari 如何按磁盘比例计算每源配额，以及如何围绕 QuotaExceededError 规划写入。

`navigator.storage.estimate()` 解析为两个字节数：当前源已经占用的 `usage`，以及它最多可以占用的保守上限 `quota`。上限是浏览器在调用时按磁盘比例算出来的，不是常量，所以只能在运行时读取，不能写死在代码里。

## 语法

```js
navigator.storage.estimate()
```

该方法不接受参数，返回一个以 `StorageEstimate` 字典兑现的 `Promise`。窗口和 worker（Service Worker 内为 `self.navigator.storage`）都可以调用，仅限安全上下文（BCD `api.StorageManager.estimate`：Chrome 61、Firefox 57、Safari 17）。

## 参数

无。

## 返回值

Promise 兑现为一个对象，含两个标准成员和一个 Chromium 扩展成员。

| 成员 | 类型 | 含义 |
|---|---|---|
| `usage` | number | 该源在受配额管理的存储（Cache Storage、IndexedDB、OPFS、Service Worker 注册）中占用的字节数。经过取整和填充，不是精确账目。 |
| `quota` | number | 该源总共可用的字节数。保守估计，随磁盘填满而缩小。 |
| `usageDetails` | object | 仅 Chromium（BCD `api.StorageManager.estimate.usageDetails`）。按存储系统细分的 `usage`；用量为 0 的系统会被省略，读取前先判断键是否存在。 |

Storage 标准允许浏览器用压缩、去重和填充对这两个数字做混淆，避免页面从精确字节数推断磁盘容量或其他站点的数据。Chromium 对不透明响应的填充最为明显：一条几 KB 的跨源 `no-cors` 响应会让 `usage` 增加约 7 MB（[Understanding storage quota](https://developer.chrome.com/docs/workbox/understanding-storage-quota)，developer.chrome.com）。

## 配额如何计算

每个引擎都从磁盘容量推导 `quota`，但公式不同（[Storage quotas and eviction criteria](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria)，developer.mozilla.org）。

| 引擎 | 每源配额 | 所有源的总上限 |
|---|---|---|
| Chromium（Chrome、Edge） | 磁盘总容量的 60%，尽力而为与持久化模式相同。无痕模式降到约 5%；开启「关闭所有窗口时清除 Cookie 和网站数据」后上限约 300 MB（[Storage for the web](https://web.dev/articles/storage-for-the-web)，web.dev）。 | 磁盘总容量的 80% |
| Firefox | 磁盘总容量的 10% 与 10 GiB 分组上限（同一站点 eTLD+1 下所有源共享）两者取小。持久化源可用磁盘的 50%，上限 8 TiB，且不受分组上限约束。 | 可用磁盘的 50% |
| WebKit（Safari 17、iOS 17、macOS 14） | 浏览器应用内约为磁盘总容量的 60%，其他应用的 `WKWebView` 内约 15%；跨源 frame 得到嵌入方配额的 10%。添加到主屏幕或 Dock 的 Web App 沿用浏览器的数值（[Updates to Storage Policy](https://webkit.org/blog/14403/updates-to-storage-policy/)，webkit.org）。 | 浏览器应用内为磁盘的 80%，其他应用内为 20% |

Safari 17 之前，每个源起始配额为 1 GiB，用满后 Safari 询问用户并按 200 MB 递增。旧版 WebKit 报告的 `quota` 会在提示之后变化，这也是每次大写入前重读而不要缓存它的又一个理由。

## 异常

| 异常 | 触发条件 |
|---|---|
| `TypeError` | 浏览器无法为该源获取存储架：源是不透明源（没有 `allow-same-origin` 的沙箱 `<iframe>`、`data:` 文档），或用户已为该站点禁用存储。Promise 被拒绝，方法本身不抛出。 |

空间不足不是 `estimate()` 的异常。超过 `quota` 的写入会在执行写入的存储里失败：`cache.put()` 以 `QuotaExceededError` `DOMException` 拒绝，IndexedDB 请求触发 `error` 事件、`request.error.name === "QuotaExceededError"` 并中止事务。

## 示例

三个示例分别用于展示用量、在大写入前决策，以及在方法缺失时降级。

### 读取估计值并显示预算行

两个成员足以显示该源用了多少。先做除法再格式化即可，两者都是字节数，普通浮点运算足够精确。

```js
const { usage, quota } = await navigator.storage.estimate();
const percent = ((usage / quota) * 100).toFixed(1);
console.log(`${(usage / 1048576).toFixed(1)} MiB / ${(quota / 1073741824).toFixed(1)} GiB（${percent}%）`);
```

在 Chromium 的 60% 规则下，桌面机的 `quota` 通常是几百 GB；接近满盘的手机则小得多。适合一台设备的预算不一定适合另一台。

### 大下载前检查余量

在已知大小的写入之前立即调用 `estimate()`，并为上文的填充留出余量。写入仍然失败时，捕获 `QuotaExceededError`，释放空间后重试一次，而不是让整个操作失败。

```js
async function cacheLargeAsset(cache, url, expectedBytes) {
  const { usage, quota } = await navigator.storage.estimate();
  if (quota - usage < expectedBytes * 1.2) {
    await pruneOldEntries(cache);
  }
  try {
    await cache.add(url);
  } catch (err) {
    if (err.name !== 'QuotaExceededError') throw err;
    await pruneOldEntries(cache);
    await cache.add(url);
  }
}
```

20% 的余量是针对取整的经验值，不来自任何规范；真正重要的是清理后的重试，因为一秒前读到的 `quota` 在一台正在同时下载照片的设备上已经过期。

### 检测支持并降级

不安全源上没有 `navigator.storage`，早于「语法」一节所列版本的浏览器也没有。把方法缺失当作「预算未知」，乐观写入并依赖存储自身的 `QuotaExceededError`。

```js
async function storageBudget() {
  if (!('storage' in navigator) || typeof navigator.storage.estimate !== 'function') {
    return null; // 预算未知：退回到在写入时捕获配额错误
  }
  try {
    return await navigator.storage.estimate();
  } catch (err) {
    if (err.name === 'TypeError') return null; // 不透明源或存储被禁用
    throw err;
  }
}
```

返回 `null` 让调用方只走一条路径：尝试写入并处理失败。这件事它本来就必须做，因为估计值只是参考。

:::observed
Chrome DevTools 的 Application > Storage 面板以用量条显示同样的数字，并提供 **Simulate custom storage quota** 复选框（Chrome 88 加入，[What's New in DevTools (Chrome 88)](https://developer.chrome.com/blog/new-in-devtools-88)，developer.chrome.com）强制压低上限。把覆盖值设到低于当前用量后，该标签页里的 `estimate()` 返回模拟的 `quota`，下一次 `cache.put()` 即以 `QuotaExceededError` 拒绝。这是不填满磁盘就能走通上面重试路径的最快方法。
:::

## 另请参阅

- [Storage: estimate() method](https://storage.spec.whatwg.org/#dom-storagemanager-estimate)（whatwg.org）
- [Updates to Storage Policy](https://webkit.org/blog/14403/updates-to-storage-policy/)（webkit.org）
- [Storage quotas and eviction criteria](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria)（developer.mozilla.org）
- [navigator.storage.persist()](/zh/reference/storage/persistence/)
- [逐出与尽力而为存储](/zh/reference/storage/eviction/)
- [CacheStorage 与 caches 全局对象](/zh/reference/storage/cache-storage/)