FileSystemWritableFileStream需用户手势触发showSaveFilePicker()获取,通过pipeTo()流式写入避免内存积压,仅Chromium102+支持,须降级处理兼容性。

可以直接用 FileSystemWritableFileStream 接收异步生成的数据流,但关键不是“怎么写”,而是“如何避免把整段数据加载进内存”。WritableStream 本身不直接创建文件,它需要由用户主动授权的 showSaveFilePicker() 触发后,通过 createWritable() 获取——这是浏览器沙箱内唯一合法的本地文件写入入口。
必须由用户手势触发授权流程
浏览器禁止静默访问本地文件系统。所有写入操作都需明确用户意图:
- 点击按钮、拖放区域、菜单项等真实交互事件中调用
showSaveFilePicker() - 不能在页面加载、定时器、fetch 回调等非手势上下文中调用,否则抛出
NotAllowedError - 可预设
types和suggestedName提升体验,例如:{ types: [{ accept: { 'application/json': ['.json'] } }], suggestedName: 'export.json' }
用 pipeTo() 连接异步数据源与 writable 流
不要手动收集数据再 write,而应让数据“自然流淌”过去。只要源头是 ReadableStream(包括 TransformStream 输出、Response.body、或自定义异步生成器封装的流),就能直接管道传输:
-
await sourceStream.pipeTo(writable)是核心调用,内置背压控制,自动调节读写节奏 - 若数据来自异步生成器,先用
ReadableStream.from(asyncGenerator)封装成流(Chrome 102+ 支持) - 示例:导出加密日志时插入 TransformStream:
logGenerator().pipeThrough(encrypter).pipeTo(writable)
处理大体积或持续生成的数据
单次 write() 调用建议不超过 128MB;真正支撑 GB 级写入的是流式管道机制:
- 网络下载场景:直接
fetch(url).then(r => r.body.pipeTo(writable)),无需等待响应完成 - 本地计算生成:用
new ReadableStream({ async pull(controller) { const chunk = await nextChunk(); controller.enqueue(chunk); } })构建按需供应的流 - 写入过程中可监听
writable.closed或捕获异常,用于清理或重试逻辑
注意兼容性与错误边界
该能力目前仅 Chromium 102+ 原生支持(Edge、新版 Opera 同步),Firefox 和 Safari 尚未实现:
- 运行前检查:
if ('showSaveFilePicker' in window),否则降级为 Blob + a.download - 常见报错:
SecurityError(非安全上下文如 HTTP)、AbortError(用户取消保存对话框)、QuotaExceededError(磁盘满或策略限制) - 不支持后台写入:页面关闭或标签页切换可能导致管道中断,需提示用户保持页面活跃











