大家好,我是 ConardLi。
多年来,Web
生态系统中已经发展出很多可用于存储的 API
,例如 IndexedDB
、localStorage
和 showNotification()
等等。
whatwg
的 Storage
标准通过定义存储的持久化、容量估算、过期时间等能力来整合这些 API
。它的出现会让浏览器存储发生什么样的变化呢,我们今天就一起来看一下。
存储桶可以解决什么问题?
传统情况下,当我们设备上的存储空间不足时,我们会选择清理垃圾,然后你会发现你的浏览器上通过使用 localStorage
、IndexedDB
等 API
存储的数据会在你无法干预的情况下丢失掉...
想象一下,我们现在有一个电子邮件应用程序。程序通过 localStorage
存储了用户还未发送的,但是仅存在于客户端的草稿,这些草稿在无感知的情况下被删除,还是挺难受的... 相比之下,如果邮件已经储在服务器上了,我们浏览器如果承受了巨大的存储压力,从客户端删除一些旧的收件箱电子邮件,这就没什么问题了。
但是,目前浏览器的所有存储 API 如 localStorage
、IndexedDB
等,存储的数据是完全平等的,一旦浏览器数据被清除,所有的数据都会被一起清理干净。
当然现在也存在一种使存储更持久化的方法,我们通过调用 StorageManager
的 persist()
方法。它会向用户发送一个许可,并在授予后将存储更改为更持久:
const persisted = await navigator.storage.persist();
if (persisted) {
/* 存储将会获得用户授权后才会被删除 */
}
但是,这种要求持久化存储的方法还是全有或全无,没有办法表达更细粒度的持久化存储需求。
这本质上可以说是一个存储桶(Storage Bucket
)。
storage-buckets
提案的核心思想就是让我们的站点可以拥有创建多个存储桶的能力,浏览器可以选择删除每个独立于其他桶的存储桶。这允许开发者能够指定清理存储的优先级,以确保最有价值的数据不会被删除。
回想一下前面的邮箱示例,我们的收件箱和草稿可以创建为具有不同优先级的存储桶,这样我们就可以按照不同的优先级来清理数据了。
如何使用 Storage Buckets API?
创建一个新的存储桶
我们可以使用 StorageBucketManager
的 open()
方法创建一个新的存储桶:
// Create a storage bucket for emails that are synchronized with the server.
const inboxBucket = await navigator.storageBuckets.open('inbox');
存储桶访问存储 API
每个存储桶都会与浏览器的存储 API
相关联,例如 IndexedDB、Cache、File
等 API
。这些存储 API
会照常工作,只是使用它们的入口点来自 StorageBucket
,例如 StorageBucket.indexedDB
。
从存储桶中访问 IndexedDB
:
const inboxDb = await new Promise(resolve => {
const request = inboxBucket.indexedDB.open("messages");
request.onupgradeneeded = () => { /* migration code */ };
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
const draftsDb = await new Promise(resolve => {
const request = draftsBucket.indexedDB.open("messages");
request.onupgradeneeded = () => { /* migration code */ };
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
从存储桶中使用 File API
:
const draftBlob = await draftsBucket.createBlob(
["Message text."], { type: "text/plain" });
const draftFile = await draftsBucket.createFile(
["Attachment data"], "attachment.txt",
{ type: "text/plain", lastModified: Date.now() });
从存储桶中访问 cache API
:
const inboxCache = await inboxBucket.caches.open("attachments");
const draftsCache = await draftsBucket.caches.open("attachments");
从存储桶中访问 Web Locks API
:
inboxBucket.locks.request("cache", lock => {
return new Promise((resolve, reject) => {
const tx = inboxDb.transaction("attachments", "readonly");
tx.oncomplete = resolve;
tx.onabort = e => reject(tx.error);
// use tx...
});
});
localStorage
或许你会有点疑惑,为啥存储桶不能控制 localStorage
呢?我们是不是可以这样用?
const settingsBucket = await navigator.storageBuckets.open("settings");
const emailsPerPage = settingsBucket.localStorage.getItem('emailsPerPage');
很遗憾,这个方案被决绝了,因为 localStorage
的一些性能问题,在存储标准中特意排除了 Web Storage API
,因此存储桶现在不能和 localStorage
配合使用...
删除存储桶
例如,下面的代码可用于在用户注销时删除设备上存储的所有数据。
代码语言:javascript复制await navigator.storageBuckets.delete("user-1234");
删除操作完成时,存储桶的数据将无法访问。例如,当删除一个桶时,它的所有
IndexedDB
数据库将被强制关闭。
枚举存储桶
获取其所有存储桶的列表:
代码语言:javascript复制const bucketNames = await navigator.storageBuckets.keys();
console.log(bucketNames); // [ "drafts", "inbox" ]
这个 API 性能比较差,最好只在调试时使用。
存储容量控制
quota
属性可以为每个应用程序设置存储使用上限,这可以确保应用程序功能中的错误不会通过耗尽整个存储的容量来影响另一个功能存储数据的能力。
const logsBucket = await navigator.storageBuckets.open("logs", {
quota: 20 * 1024 * 1024 // 20 MB
}
以下的代码可以获取当前存储桶的空间使用情况,你可以在:
代码语言:javascript复制const inboxEstimate = await inboxBucket.estimate();
if (inboxEstimate.usage >= inboxEstimate.quota * 0.95) {
displayWarningButterBar("Go to settings and sync fewer days of email");
}
存储过期时间
存储桶的过期策略可确保在特定过期时间后站点将无法使用存储桶的数据。这个策略和 HTTP cookie
的 expires
属性类似 。
设定存储桶的过期时间:
代码语言:javascript复制const twoWeeks = 14 * 24 * 60 * 60 * 1000;
const newsBucket = await navigator.storageBuckets.open("news", {
expires: Date.now() twoWeeks });
可以随时查询桶的过期时间:
代码语言:javascript复制if ((await newsBucket.expires()) === null) {
// This should not happen. The browser must always honor the expires policy.
showWarningButterBar("");
}
只要存储桶未过期,就可以随时更改存储桶的过期时间。
代码语言:javascript复制const oneDay = 24 * 60 * 60 * 1000;
if (await newsBucket.expires() - Date.now() <= oneDay) {
await refreshNews(newsBucket);
await newsBucket.setExpires(Date.now() twoWeeks);
}
存储桶的持久化
为确保存储桶被持久化,你可以向 open()
方法传递 durability
和 persisted
两个参数:
persisted
确定存储桶是否应该被持久化(默认 false)。durability
可以提供更细粒度的控制能力,主要帮助浏览器权衡写入性能和降低电源故障时数据丢失的风险。允许的值为'relaxed'
(默认)或'strict'
:'strict'
:将断电时数据丢失的风险降至最低,这意味着写入可能需要更长的时间才能完成,可能会影响整体系统性能,消耗更多的电池电量,并且可能会加快存储设备磨损的速度;'relaxed:'
当发生断电时,存储桶可能会“忘记”在最后几秒钟内完成的写入,写入速度会更快,耗电以及对存储设备的磨损更小。
// Create a storage bucket for email drafts that only exist on the client.
const draftsBucket = await navigator.storageBuckets.open('drafts', {
durability: 'strict', // Or `'relaxed'`.
persisted: true, // Or `false`.
});
试用 Storage Buckets
目前存储桶还没登陆浏览器的正式版,但是你可以开启 Chrome
的试验特性 falg #enable-experimental-web-platform-features
来使用它。
最后
参考链接:
- https://developer.chrome.com/en/blog/storage-buckets/
- https://wicg.github.io/storage-buckets/explainer