存储 · API
navigator.storage.getDirectory()(OPFS)
发布于
navigator.storage.getDirectory() 解析为源私有文件系统(OPFS)的根 FileSystemDirectoryHandle。OPFS 是受配额管理的文件与目录存储,没有选择器或权限提示把关,用户在文件管理器里也看不到它。在专用 worker 内,FileSystemFileHandle.createSyncAccessHandle() 再加上同步、按偏移寻址的读写,这正是 SQLite 等 Wasm 数据库能在浏览器里实用的原因。
navigator.storage.getDirectory()fileHandle.createSyncAccessHandle()fileHandle.createSyncAccessHandle({ mode })getDirectory() 在窗口和 worker 中都可用(BCD api.StorageManager.getDirectory:Chrome 86、Firefox 111、Safari 15.2)。createSyncAccessHandle() 只存在于专用 worker(Chrome 102、Firefox 111、Safari 15.2),而且名字虽叫 sync,本身返回的是 Promise;真正同步的是返回的 FileSystemSyncAccessHandle 上的方法(read、write、getSize、truncate、flush、close)。OPFS 文件句柄上的异步 createWritable() 到 Safari 26 才加入,所以在 Safari 15.2 到 18 上同步访问句柄是唯一的写入路径。
| 方法 | 参数 | 类型 | 含义 |
|---|---|---|---|
getDirectory |
无 | 返回该源的 OPFS 根。每个源以及被嵌入源的每个存储分区各有一个根。 | |
createSyncAccessHandle |
options.mode |
string,可选 | 锁模式。"readwrite"(默认):每个文件同时只能有一个句柄。"read-only":可同时打开任意多个句柄,但只能调用 read()、getSize()、close()。"readwrite-unsafe":可同时打开任意多个可写句柄,没有任何协调,一致性由调用方负责。 |
根下的文件和目录通过 getFileHandle(name, { create }) 与 getDirectoryHandle(name, { create }) 获取,通过 removeEntry(name, { recursive }) 删除。用量计入该源的配额并体现在 navigator.storage.estimate() 中;清除站点数据会删除整棵树。
| 异常 | 位置 | 触发条件 |
|---|---|---|
SecurityError DOMException |
getDirectory() |
浏览器无法为该源映射目录,例如不透明源或存储被禁用。 |
NotFoundError DOMException |
getFileHandle()、createSyncAccessHandle() |
具名条目不存在且未传 create: true,或文件在取得句柄后被删除。 |
TypeMismatchError DOMException |
getFileHandle()、getDirectoryHandle() |
该名称存在,但是另一种类型的条目。 |
NoModificationAllowedError DOMException |
createSyncAccessHandle() |
拿不到锁:另一个 "readwrite" 模式的句柄仍打开着,或该文件上有打开的 FileSystemWritableFileStream。常见原因是第二个标签页的 worker 打开了同一个数据库文件。 |
InvalidStateError DOMException |
createSyncAccessHandle()、已关闭句柄上的任何方法 |
句柄不指向 OPFS(来自选择器的句柄),或已经调用过 close()。 |
NotAllowedError DOMException |
createSyncAccessHandle() |
"readwrite" 模式下该句柄的权限状态不是 granted。 |
QuotaExceededError DOMException |
write()、truncate() |
该源配额已用尽。文件保留失败之前已写入的字节。 |
write() 返回写入的字节数,但在 flush() 返回之前不保证落盘;在两者之间被终止的 worker 可能丢掉写入的尾部。各异常的完整条件见 FileSystemFileHandle: createSyncAccessHandle() method(developer.mozilla.org)。
worker 示例假定用 new Worker() 创建的专用 worker;共享 worker 和 Service Worker 拿不到同步句柄。
在专用 worker 中向日志文件追加
Section titled “在专用 worker 中向日志文件追加”主线程发来一行文本;worker 在页面生命周期内持有一个 "readwrite" 句柄,并在当前大小处追加。按消息开关句柄也可以,但每次都要付出一次加锁往返。
// log-worker.js(专用 worker)let handle;
self.onmessage = async ({ data: line }) => { if (!handle) { const root = await navigator.storage.getDirectory(); const file = await root.getFileHandle('app.log', { create: true }); handle = await file.createSyncAccessHandle(); } const bytes = new TextEncoder().encode(`${line}\n`); handle.write(bytes, { at: handle.getSize() }); handle.flush();};句柄是独占的,第二个标签页启动同一个 worker 时 createSyncAccessHandle() 会抛出 NoModificationAllowedError;页面应捕获它,然后要么改用 "readwrite-unsafe" 并自己加锁(例如 Web Locks API),要么把写入集中到一个标签页。
在主线程读回文件
Section titled “在主线程读回文件”API 的异步部分在窗口里可用。getFile() 返回 File,所以普通的 text()、arrayBuffer()、stream() 读取方式都适用。
async function readLog() { const root = await navigator.storage.getDirectory(); try { const file = await (await root.getFileHandle('app.log')).getFile(); return await file.text(); } catch (err) { if (err.name === 'NotFoundError') return ''; // 还没写过日志 throw err; }}来自 OPFS 的 File 是快照:getFile() 兑现之后 worker 追加的字节通过它看不到,要读新内容就重新从句柄取文件。
检测支持并选择存储
Section titled “检测支持并选择存储”createSyncAccessHandle 只暴露给专用 worker,在主线程上检测即使在 Chrome 里也是 false。把探测放进 worker 并把结果发回,由页面据此选择存储。
// probe-worker.js(专用 worker)(async () => { if (!('storage' in navigator) || typeof navigator.storage.getDirectory !== 'function') { postMessage('none'); // 没有 OPFS:页面退回 IndexedDB return; } const sync = typeof FileSystemFileHandle !== 'undefined' && 'createSyncAccessHandle' in FileSystemFileHandle.prototype; postMessage(sync ? 'sync' : 'async');})();'sync' 表示上面的日志 worker 路径可用;'async' 对应暴露了根但没有同步句柄的引擎,这时数据库仍然更适合放在 IndexedDB。
- File System Standard: createSyncAccessHandle()(whatwg.org)
- Origin private file system(developer.mozilla.org)
- File System Access API
- StorageManager.estimate()
- IndexedDB
规范
| 规范 | 状态 |
|---|---|
| 无。 | |