在 Web 环境中,一般对内容的存储都是依托于 Cookie 或是 LocalStorage 进行的(个别会使用 IndexDB)。其实,在早些时候,Web 曾推出过一个 FileSystem 的标准(已经废弃),用于将数据直接存储到本地的沙盒环境中,方便日后的使用。这个 API 目前只有 Chrome 进行了实现。
这篇文章 针对 FileSystem API 做了详细的介绍。这个 GitHub 仓库 则在 FileSystem 原生 API 的基础上,进行了二次封装。(注:第一个链接给出的文章,部分代码有误,可能无法正常运行。实际使用过程中,可以参考第二个链接给出的 GitHub 仓库中的相关代码进行调整)
假设,需要实现一个分片的文件下载功能,即文件被服务器分割成很多块,通过 JavaScript 依次下载这些内容,再在本地拼接后提交给用户。这里,考虑到文件可能非常大,如果只是存储在内存中,一旦用户刷新页面或是遇到其他问题,已经下载的内容就都失效了,只能重新再来一次。这种情况下,可以考虑使用 FileSystem API 将分片的文件内容下载后先存放在本地的沙盒文件中,等到全部下载完成之后,再将拼接好的内容提交给用户。
下面给出一个实例代码,用以介绍 FileSystem API 的可能使用方法:
/**
* 实际中 Chrome 给出的 API 只用 window.webkitRequestFileSystem
*/
const requestFileSystem = window.requestFileSystem ||
window.webkitRequestFileSystem;
/**
* 下载 Link 并保存文件为 filename
* 只是示例代码,实际的可行方案请参考 file-saver 的实现
*/
function download(link, filename) {
const a = document.createElement('a');
a.href = link;
a.target = '_blank';
a.download = filename;
a.click();
}
function save(blob, filename) {
function errorHandler(e) {
console.log(e);
}
function handler(fs) {
/**
* 获取名为 filename 的文件,{ create: true } 表示如果文件不存在,就创建一个
* fileEntry 中包含的 API 可以用于对这个文件进行操作
*/
fs.root.getFile(filename, { create: true }, (fileEntry) => {
fileEntry.createWriter(writer => {
/**
* 指定文件的写入位置在当前文件内容的末尾
*/
writer.seek(writer.length);
writer.onwriteend = () => {
/**
* FileSystem 中的文件,可以通过类似如下的 Link 获取到:
* filesystem:https://xxx.com/persistent/filename
* 具体的 URL 地址通过 `fileEntry.toURL()` 获取
*/
const url = fileEntry.toURL();
download(url, filename);
};
writer.onerror = console.error;
writer.write(blob);
}, errorHandler);
}, errorHandler);
}
/**
* 对于 PERSISTENT 存储的文件,需要事先通过浏览器询问权限
* 声明需要使用的大小为 blob.size
* 第二个参数是 success callback,在成功后调用,可以在这里进行文件读写
* 第三个参数是 error callback,用于处理报错
*/
navigator.webkitPersistentStorage.requestQuota(
blob.size,
grantedBytes => {
/**
* 以 PERSISTENT 的方式,写入 grantedBytes 这么多的内容
* 允许写入会执行 handler,否则会执行 errorHandler
*/
requestFileSystem(window.PERSISTENT, grantedBytes, handler, errorHandler);
},
console.error
);
}
/**
* 示例代码的调用,将 hello world 写入到 output.txt 文件中
*/
save(new Blob(['hello world'], { type: 'text/plain' }), 'output.txt')
上面这个例子,展示了如何将 Blob / File 写入到本地沙盒的文件中(例子中写入到了 output.txt
文件内)。有几点需要注意:
- 文件是写入到沙盒环境中的,因而虽然
fileEntry.fullPath
的值是/output.txt
,并不代表真的可以在根目录下找到 output.txt 文件 window.PERSISTENT
和window.TEMPORARY
是两种可能的存储方式。如果是PERSISTENT
的,那么需要用户授权(也就是requestQuota
做的事情)且清理需要程序或用户手动执行;如果是TEMPORARY
类型的存储方式,那么浏览器可能会在某些情况下自动清理文件(比如,当空间不够的时候)- 通过
fileEntry.toURL
API 可以拿到当前文件存储对应的 URL 地址,进而可以通过常规手段将这个内容下载到本地 - 代码中的
errorHandler
函数写的比较粗糙,更丰富的 Error Handler 写法,可以参考 chromestore.js 中的代码
MyAirBridge 网站可能使用了类似上面提到的技术来存储下载中的文件内容。