# StorageManager.persist() 与 persisted()

> navigator.storage.persist() 如何申请把源提升为持久化存储、Chrome、Firefox 与 Safari 如何处理该申请，以及如何为返回 false 编码。

`navigator.storage.persist()` 请求浏览器把该源的存储桶标记为持久化，从而让 Cache Storage、IndexedDB、OPFS 和 Service Worker 注册免于存储压力下的自动逐出。`navigator.storage.persisted()` 报告这个标记是否已设置。两者都不改变配额，只改变浏览器能否在用户没有要求的情况下清除数据。

## 语法

```js
navigator.storage.persist()
navigator.storage.persisted()
```

两者都返回 `Promise<boolean>`。`persist()` 在调用后存储桶处于持久化模式时兑现为 `true`（不论是刚授予还是原本就是），浏览器拒绝时为 `false`。`persisted()` 只读取当前模式。`persist()` 暴露在 `Window` 上、worker 内没有；`persisted()` 两处都有（BCD `api.StorageManager.persist`：Chrome 55、Firefox 57、Safari 15.2）。两者都要求安全上下文。

## 参数

无。

## 异常

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

被拒绝不是异常。浏览器认为站点尚未赢得持久化资格时，`persist()` 兑现为 `false`，源仍保持尽力而为模式。

## 各浏览器如何决定

Storage 标准把决定权留给浏览器，三个引擎走了三条不同的路（[Storage quotas and eviction criteria](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria)，developer.mozilla.org）。

| 引擎 | 调用 `persist()` 时的行为 | 影响决定的因素 |
|---|---|---|
| Chromium（Chrome、Edge） | 不弹提示。请求被静默授予或拒绝，被拒后可以稍后再次申请（[Persistent storage](https://web.dev/articles/persistent-storage)，web.dev）。 | 站点参与度得分、站点是否已安装或加入书签、是否已授予 `notifications` 权限。 |
| Firefox | 显示权限气泡并等待用户。 | 用户的点击。Firefox 会按站点记住拒绝。 |
| WebKit（Safari 15.2+） | 不弹提示；根据用户与站点的交互历史自动授予或拒绝。 | 此前与该源的第一方交互。 |

Chromium 的授予取决于参与度，因此首次访问首屏时的调用最可能返回 `false`。在用户安装应用、开启通知或完成一次有意义的操作之后再调用，不改代码也能提高成功率。

脚本没有撤销持久化的 API。用户通过站点设置（Chrome：`chrome://settings/content/siteDetails?site=<源>`；Firefox：页面信息里的权限一节）或清除站点数据来撤销。

## 示例

示例把「申请」（参与度信号之后做一次）和「读取」（每次启动）分开，这正是两个方法的预期用法。

### 在参与度信号之后申请持久化

把申请绑定到用户做出「这个站点有价值」动作的时刻，并根据布尔值行动而不是假设成功。下面的例子在安装成功或通知权限被授予之后运行。

```js
async function requestDurableStorage() {
  if (await navigator.storage.persisted()) return true;
  const granted = await navigator.storage.persist();
  if (!granted) {
    console.info('存储仍为尽力而为模式；被逐出的排队任务会重新同步。');
  }
  return granted;
}
```

返回 `false` 时应用照常工作：写入 IndexedDB 或 Cache Storage 的数据在出现存储压力之前都在，Safari 则是在七天浏览器使用天数内无交互之前都在（见[逐出与尽力而为存储](/zh/reference/storage/eviction/)）。把同步层设计成「缺失的记录重新拉取或重新推导」，而不是当作数据损坏。

### 在设置页里报告当前模式

`persisted()` 是廉价的读取；每次启动都调用，不要缓存结果，因为用户可能在两次访问之间撤销持久化，Chromium 也可能在参与度上升后授予之前拒绝过的申请。

```js
async function describeStorageMode() {
  const persistent = await navigator.storage.persisted();
  return persistent
    ? '离线数据不会被自动清除。'
    : '设备空间不足时离线数据可能被清除。';
}
```

把这句话和 [`StorageManager.estimate()`](/zh/reference/storage/quota-estimate/) 的用量数字放在一起，设置页就同时说明了存了多少、有多安全。

### 检测支持并降级

不安全源上没有 `navigator.storage`，早于 Chrome 55、Firefox 57、Safari 15.2 的浏览器也没有。把方法缺失当作尽力而为存储处理。

```js
async function ensurePersistence() {
  if (!('storage' in navigator) || typeof navigator.storage.persist !== 'function') {
    return false; // 不支持：按尽力而为存储处理
  }
  try {
    return await navigator.storage.persist();
  } catch (err) {
    if (err.name === 'TypeError') return false; // 不透明源或存储被禁用
    throw err;
  }
}
```

函数对「不支持」「被拒绝」「存储被禁用」返回同一个 `false`，这是有意为之：调用方只有一条回退路径，三种情况下都一样。

:::observed
Firefox（英文界面）把 `persist()` 请求渲染为锚定在地址栏的气泡，文案为 **Allow example.com to store data in persistent storage?**，对应 [browser.properties](https://github.com/mozilla-firefox/firefox/blob/main/browser/locales/en-US/chrome/browser/browser.properties)（github.com）中的 `persistentStorage.allowWithSite2`。拒绝后地址栏显示带删除线的存储图标，悬停提示为 **You have blocked persistent storage for this website.**（`browser.ftl` 中的 `urlbar-persistent-storage-blocked`），之后的 `persist()` 调用直接兑现为 `false`，不再弹出提示，直到用户清除该拦截。
:::

## 另请参阅

- [Storage: persist() method](https://storage.spec.whatwg.org/#dom-storagemanager-persist)（whatwg.org）
- [Persistent storage](https://web.dev/articles/persistent-storage)（web.dev）
- [Storage quotas and eviction criteria](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria)（developer.mozilla.org）
- [逐出与尽力而为存储](/zh/reference/storage/eviction/)
- [StorageManager.estimate()](/zh/reference/storage/quota-estimate/)
- [IndexedDB](/zh/reference/storage/indexeddb/)