Web 平台正日益为开发者提供所需的工具,以便他们构建精细调整的高性能 Web 应用。最值得一提的是,WebAssembly (Wasm) 为快速而强大的 Web 应用打开了大门,而 Emscripten 等技术现在允许开发者在 Web 上重复使用经过测试的代码。为了充分发挥这一潜力,开发者在存储方面必须拥有相同的能力和灵活性。
这就是 Storage Foundation API 的用武之地。Storage Foundation API 是一种新的快速且不带预设的存储 API,可为 Web 带来新的且备受期待的用例,例如实现高性能数据库和妥善管理大型临时文件。借助这一新接口,开发者可以“自带存储空间”来开发 Web 应用,从而缩小 Web 代码与平台专用代码之间的功能差距。
存储基础 API 旨在模仿非常基本的文件系统,因此通过提供通用、简单且高性能的原语,让开发者能够灵活地构建更高级别的组件。应用可以根据自身需求选择最合适的工具,在易用性、性能和可靠性之间找到恰当的平衡。
为什么 Web 需要另一个存储 API?
Web 平台为开发者提供了多种存储选项,每种选项都是针对特定应用场景而构建的。
- 其中一些选项显然与此提案不重叠,因为它们仅允许存储极少量的数据,例如 Cookie 或由
sessionStorage
和localStorage
机制组成的 Web Storage API。 - 其他选项已因各种原因而被弃用,例如 File and Directory Entries API 或 WebSQL。
- File System Access API 具有类似的 API Surface,但其用途是与客户端的文件系统进行交互,并提供对可能超出来源甚至浏览器所有权范围的数据的访问权限。这种不同的侧重点带来了更严格的安全考虑因素和更高的性能成本。
- IndexedDB API 可用作某些 Storage Foundation API 用例的后端。例如,Emscripten 包含基于 IndexedDB 的持久性文件系统 IDBFS。不过,由于 IndexedDB 本质上是一个键值存储区,因此存在明显的性能限制。此外,在 IndexedDB 下,直接访问文件的子部分会更加困难且速度更慢。
- 最后,CacheStorage 接口得到广泛支持,经过调整可用于存储大型数据(例如 Web 应用资源),但这些值是不可变的。
Storage Foundation API 旨在弥合之前存储选项的所有不足,允许在应用来源内高效存储定义的可变大文件。
Storage Foundation API 的建议用例
以下是一些可能使用此 API 的网站示例:
- 处理大量视频、音频或图片数据的效率类应用或创意类应用。此类应用可以将段卸载到磁盘,而不是将其保留在内存中。
- 依赖于可从 Wasm 访问的持久性文件系统且需要比 IDBFS 所能保证的更高性能的应用。
什么是 Storage Foundation API?
该 API 主要包含两个部分:
- 文件系统调用,提供与文件和文件路径交互的基本功能。
- 文件句柄,用于提供对现有文件的读取和写入访问权限。
文件系统调用
Storage Foundation API 引入了一个新对象 storageFoundation
,该对象位于 window
对象上,包含多项功能:
storageFoundation.open(name)
:打开具有指定名称的文件(如果存在),否则创建新文件。返回一个 promise,该 promise 会解析为已打开的文件。
storageFoundation.delete(name)
:移除具有指定名称的文件。返回一个在文件被删除时解析的 promise。storageFoundation.rename(oldName, newName)
:以原子方式将文件从旧名称重命名为新名称。返回一个在文件重命名时解析的 promise。storageFoundation.getAll()
:返回一个 Promise,该 Promise 会解析为包含所有现有文件名的数组。storageFoundation.requestCapacity(requestedCapacity)
:为当前执行上下文请求新的容量(以字节为单位)。返回一个 promise,该 promise 会解析为剩余的可用容量。
storageFoundation.releaseCapacity(toBeReleasedCapacity)
:从当前执行上下文中释放指定数量的字节,并返回一个以剩余容量解析的 promise。storageFoundation.getRemainingCapacity()
:返回一个 Promise,该 Promise 会解析为当前执行上下文可用的容量。
文件句柄
您可以通过以下函数处理文件:
NativeIOFile.close()
:关闭文件,并返回一个在操作完成时解析的 promise。NativeIOFile.flush()
:将文件的内存中状态与存储设备同步(即刷新),并返回一个在操作完成时解析的 promise。
NativeIOFile.getLength()
:返回一个 Promise,该 Promise 会解析为文件的长度(以字节为单位)。NativeIOFile.setLength(length)
:设置文件的长度(以字节为单位),并返回一个在操作完成时解析的 promise。如果新长度小于当前长度,则从文件末尾开始移除字节。否则,该文件会扩展为包含零值字节。NativeIOFile.read(buffer, offset)
:通过传输给定缓冲区后得到的缓冲区读取给定偏移处的文件内容,然后使该缓冲区处于分离状态。返回一个NativeIOReadResult
,其中包含已转移的缓冲区和成功读取的字节数。NativeIOReadResult
是一个包含两个条目的对象:buffer
:一个ArrayBufferView
,它是传递给read()
的缓冲区的转移结果。它与源缓冲区的类型和长度相同。readBytes
:已成功读入buffer
的字节数。如果发生错误或读取范围超出文件末尾,则此值可能小于缓冲区大小。如果读取范围超出文件末尾,则将其设置为零。
NativeIOFile.write(buffer, offset)
:将指定缓冲区的内容写入指定偏移处的文件中。缓冲区在写入任何数据之前即已转移,因此处于分离状态。 返回一个NativeIOWriteResult
,其中包含已转移的缓冲区和成功写入的字节数。如果写入范围超出文件长度,文件将会扩展。NativeIOWriteResult
是一个包含两个条目的对象:buffer
:一个ArrayBufferView
,它是传递给write()
的缓冲区的转移结果。它与源缓冲区的类型和长度相同。writtenBytes
:已成功写入buffer
的字节数。如果发生错误,此值可能小于缓冲区大小。
完整示例
为了更清楚地说明上述概念,下面提供了两个完整的示例,引导您了解 Storage Foundation 文件生命周期的不同阶段。
打开、写入、读取、关闭
// Open a file (creating it if needed). const file = await storageFoundation.open('test_file'); try { // Request 100 bytes of capacity for this context. await storageFoundation.requestCapacity(100); const writeBuffer = new Uint8Array([64, 65, 66]); // Write the buffer at offset 0. After this operation, `result.buffer` // contains the transferred buffer and `result.writtenBytes` is 3, // the number of bytes written. `writeBuffer` is left detached. let result = await file.write(writeBuffer, 0); const readBuffer = new Uint8Array(3); // Read at offset 1. `result.buffer` contains the transferred buffer, // `result.readBytes` is 2, the number of bytes read. `readBuffer` is left // detached. result = await file.read(readBuffer, 1); // `Uint8Array(3) [65, 66, 0]` console.log(result.buffer); } finally { file.close(); }
打开、列出、删除
// Open three different files (creating them if needed). await storageFoundation.open('sunrise'); await storageFoundation.open('noon'); await storageFoundation.open('sunset'); // List all existing files. // `["sunset", "sunrise", "noon"]` await storageFoundation.getAll(); // Delete one of the three files. await storageFoundation.delete('noon'); // List all remaining existing files. // `["sunrise", "noon"]` await storageFoundation.getAll();
安全与权限
Chromium 团队在设计和实现 Storage Foundation API 时,遵循了控制对强大的 Web 平台功能的访问权限中定义的核心原则,包括用户控制、透明度和人体工程学。
与 Web 上的其他现代存储 API 类似,对 Storage Foundation API 的访问受来源限制,这意味着来源只能访问自行创建的数据。它还仅限于安全上下文。
用户控制
存储空间配额将用于分配磁盘空间访问权限并防止滥用。您需要先请求要占用的内存。与其他存储 API 一样,用户可以通过浏览器清除 Storage Foundation API 占用的空间。
实用链接
- 公开说明
- Chromium 跟踪 bug
- ChromeStatus.com 条目
- Blink 组件:
Blink>Storage>NativeIO
- TAG Review
- 从意图到原型
- WebKit 线程
- Mozilla 线程
致谢
Storage Foundation API 由 Emanuel Krivoy 和 Richard Stotz 规范和实现。本文已由 Pete LePage 和 Joe Medley 审核。
主打图片来自 Markus Spiske on Unsplash。