浏览器的Persistent Storage,你真的了解吗?

1,956 阅读4分钟

简介

一般web应用存储数据到本地有多种方式. 比如indexedDb,Cache Api, 或者localStorage. 当然, 所有的这些存储,都会占用本地机器的存储空间.

当本地的存储空间不足时, 浏览器会自动清除这些本地存储, 以获得更多可用的存储空间. 针对离线应用或者PWA, 这是非常不幸的. 可能本地的数据还没有同步到服务器, 或者应用本身就是希望提供离线访问的能力. 不过好消息是, 浏览器给我们提供了两种存储模式:

  1. best effort : 临时存储
  2. persistent: 持久化存储

默认的模式, 当然是"best effort", 存储空间不足时, 会被浏览器自动清理. 但是"persistent"模式, 浏览器不会自动清除, 需要用户手动,才能清除。

如何申请"persistent"存储模式

我们可以通过以下代码申请.

/**
 * 申请持久存储.
 */
function grantPersist() {
  return wrapPromise((resolve, reject) => {
    if (navigator.storage && navigator.storage.persist) {
      navigator.storage.persist().then(granted => {
        // granted: true / false
        if(granted){
            console.log("持久化存储模式, 需要用户手动清除")
        }else{
            console.log("临时存储模式, 存储空间不足时, 浏览器会自动清除")
        }
        resolve(granted);
      });
    } else {
      reject('navigator.storage.persist: 不支持');
    }
  });
}

是否需要用户授权?

  1. 在chrome 55以后, 站点满足一下任意一个条件时, 浏览器会自动授权. 无需用户确认.
    • 该站点已添加书签, 并且用户的书签数小于等于5个
    • 站点有很高的"site engagement". 通过这个命令可以查看: chrome://site-engagement/
    • 站点已添加到主屏幕
    • 站点启用了push通知功能.

除了以上几种情况, 其他的任何情况都会被自动拒绝.

  1. 在firefox上: 会有一个弹框, 需要用户授权.

如果查看当前的存储模式

我们可以调用"navigator.storage.persist"api来查看

/**
 * 查看存储模式
 */
function checkPersisted() {
  return wrapPromise((resolve, reject) => {
    if (navigator.storage && navigator.storage.persisted) {
      navigator.storage.persisted().then(persisted => {
          if(persisted){
              console.log('当前的模式是持久化的,本地存储的数据需要用户手动清除')
          }esle{
              console.log('当前的模式是临时的, 浏览器在空间不足时, 会自动清除本地数据')
          }
          resolve(persisted)
      });
    } else {
      reject('navigator.storage.persisted 不支持');
    }
  });
}

能否查看到可用的存储空间和已使用的空间?

我们可以通过api来查看

/**
 * 获取可使用磁盘空间和已使用的空间的信息.
 */
function estimateSpace() {
  return new Promise((resolve, reject) => {
    if (navigator.storage && navigator.storage.estimate) {
      navigator.storage.estimate().then(estimate => {
        // 原始的单位是byte. 转成MB
        const ut = 1024 * 1024;
        resolve({
          total: estimate.quota / ut,
          usage: estimate.usage / ut
        });
      });
    } else {
      reject('navigator.storage.estimate: 不支持');
    }
  });
}

浏览器兼容情况

浏览器兼容性

demo, 完整的代码

  • util.js
function wrapPromise(cb) {
  return new Promise((resolve, reject) => cb(resolve, reject));
}

/**
 * 用户授权。申请持久存储.
 */
function grantPersist() {
  return wrapPromise((resolve, reject) => {
    if (navigator.storage && navigator.storage.persist) {
      navigator.storage.persist().then(granted => {
        // granted: true/false
        resolve(granted);
      });
    } else {
      reject('navigator.storage.persist: the browser is not supported');
    }
  });
}

/**
 * 查看存储模式
 */
function checkPersisted() {
  return wrapPromise((resolve, reject) => {
    if (navigator.storage && navigator.storage.persisted) {
      navigator.storage.persisted().then(result => resolve(result));
    } else {
      reject('navigator.storage.persisted is not support');
    }
  });
}

/**
 * 获取可使用磁盘空间和已使用的空间的信息.
 */
function estimateSpace() {
  return new Promise((resolve, reject) => {
    if (navigator.storage && navigator.storage.estimate) {
      navigator.storage.estimate().then(estimate => {
        // 原始的单位是byte. 转成MB
        const ut = 1024 * 1024;
        resolve({
          total: estimate.quota / ut,
          usage: estimate.usage / ut
        });
      });
    } else {
      reject('navigator.storage.estimate: the browser is not supported');
    }
  });
}

  • index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <script src="./util.js"></script>
  </head>
  <body>
    <button id="btn">用户授权</button>

    <h1>浏览器存储信息</h1>
    <h1 id="diskInfo"></h1>
    <hr />
    <h1>当前的存储模式是:</h1>
    <h1 id="persistInfo"></h1>

    <script>
      // 检查浏览器的存储模式.
      function getPersisted() {
        checkPersisted().then(
          persisted => {
            document.querySelector('#persistInfo').innerHTML = persisted
              ? '持久'
              : '临时';
          },
          error => {
            document.querySelector('#persistInfo').innerHTML = error;
          }
        );
      }

      // 申请持久化存储权限.
      function applyPersist() {
        grantPersist().then(
          result => {
            getPersisted();
          },
          error => {
            document.querySelector('#persistInfo').innerHTML = error;
          }
        );
      }

      // 获取可使用磁盘空间和已使用的空间的信息
      function checkStorageSpace() {
        estimateSpace().then(
          data => {
            const { total, usage } = data;

            document.querySelector('#diskInfo').innerHTML = `总大小: ${(
              total / 1024
            ).toFixed(4)}GB, 已使用${(usage / 1024).toFixed(4)}GB`;
          },
          error => {
            document.querySelector('#diskInfo').innerHTML = error;
          }
        );
      }

      function init() {
        checkStorageSpace();
        getPersisted();

        document.querySelector('#btn').onclick = function() {
          applyPersist();
        };
      }

      window.onload = init;
    </script>
  </body>
</html>

  • 应用后的截图

code