简介
HTML5 FileSystem API 和 Web Worker 在很大程度上决定了 为自己加油打气。FileSystem API 终于提供分层存储和 为 Web 应用和 Worker 实现文件 I/O,从而实现了真正的异步“多线程” 到 JavaScript。不过,当您结合使用这些 API 时,您可以构建一些真正有趣的应用。
本教程提供了使用 HTML5 的指南和代码示例 Web Worker 中的 FileSystem。学习本课程前,您需要具备以下知识: 两个 API如果您还没有准备好深入学习或有兴趣学习 如需详细了解这些 API,请阅读两篇优秀的教程,了解相关基础知识: 探索 FileSystem API 和 Web Worker 基础知识。
同步 API 与异步 API
异步 JavaScript API 可能很难使用。它们很大。它们很复杂。 但最令人失望的是,它们提供了大量导致出错的机会。 最后一项任务是在复杂的异步 API (FileSystem) 上分层 好消息是, FileSystem API 定义了一个同步版本,以缓解 Web Workers 中的难点。
在大多数情况下,同步 API 与其异步同类 API 完全相同。 方法、属性、特性和功能应是用户熟悉的。主要差异包括:
- 同步 API 只能在 Web Worker 环境中使用,而 可在 worker 内外使用异步 API。
- 回调已输出。API 方法现在会返回值。
- 窗口对象的全局方法(
requestFileSystem()
和resolveLocalFileSystemURL()
)变为“requestFileSystemSync()
”,并且resolveLocalFileSystemSyncURL()
。
除了这些例外情况,API 均相同。好的,可以出发了!
请求文件系统
Web 应用可通过请求
LocalFileSystemSync
对象。requestFileSystemSync()
可供 Worker 的全局范围使用:
var fs = requestFileSystemSync(TEMPORARY, 1024*1024 /*1MB*/);
请注意新的返回值,因为我们现在使用的是同步 API, 成功和错误回调的概率。
与普通的 FileSystem API 一样,方法目前带有前缀:
self.requestFileSystemSync = self.webkitRequestFileSystemSync ||
self.requestFileSystemSync;
处理配额
目前,您无法在工作器上下文中请求 PERSISTENT
配额。我建议处理工作器之外的配额问题。
该过程可能如下所示:
- worker.js:将所有 FileSystem API 代码封装在
try/catch
中,以便 捕获到QUOTA_EXCEED_ERR
错误。 - worker.js:如果您捕获
QUOTA_EXCEED_ERR
,请将postMessage('get me more quota')
发送回主应用。 - 主应用:在收到 #2 时跳
window.webkitStorageInfo.requestQuota()
舞。 - 主应用:在用户授予更多配额后,发回
postMessage('resume writes')
以告知工作器额外的存储空间。
这是一个相当复杂的权宜解决方法,但应该有效。如需详细了解如何通过 FileSystem API 使用 PERSISTENT
存储空间,请参阅申请配额。
使用文件和目录
getFile()
和 getDirectory()
的同步版本会返回 FileEntrySync
和 DirectoryEntrySync
。
例如,以下代码可创建一个名为“log.txt”的空文件在 根目录下。
var fileEntry = fs.root.getFile('log.txt', {create: true});
以下命令将在根文件夹中创建一个新目录。
var dirEntry = fs.root.getDirectory('mydir', {create: true});
处理错误
如果您从未调试过 Web Worker 代码,真是太好了!这可能是个真正的痛苦 找出问题所在。
同步环境中缺少错误回调
如果我们加大调试 Web Worker 代码的一般复杂性,
你很快就会感到沮丧的。为了让生活更轻松
在 try/catch 中找到相关的 Worker 代码。然后,如果发生任何错误,请转发
使用 postMessage()
将错误传递给主应用:
function onError(e) {
postMessage('ERROR: ' + e.toString());
}
try {
// Error thrown if "log.txt" already exists.
var fileEntry = fs.root.getFile('log.txt', {create: true, exclusive: true});
} catch (e) {
onError(e);
}
传递文件、Blob 和 ArrayBuffer
Web Worker 在首次出现时只允许字符串数据
postMessage()
发送。后来,浏览器开始接受可序列化数据,
这意味着可以传递 JSON 对象。不过,最近一些浏览器(如 Chrome)
接受使用postMessage()
结构化克隆算法。
这实际上意味着什么?这意味着要简单得多
在主应用和工作器线程之间传递二进制数据。支持结构化克隆的浏览器
对于 Worker,您可以传递类型化数组、ArrayBuffer
、File
或 Blob
。
转换为 worker。虽然数据仍然是副本,但能够传递 File
意味着
与前一种方法相比,这种方法的性能优势在于对文件进行 base64 编码,
然后再将其传递到 postMessage()
中。
以下示例将用户选择的文件列表传递给专用 worker。
Worker 直接传递文件列表(很容易显示返回的数据)
实际上是一个 FileList
),并且主应用会将每个文件读取为 ArrayBuffer
。
此示例还使用了改进版的内嵌 Web Worker 技术。 Web Worker 基础知识中所述。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>Passing a FileList to a Worker</title>
<script type="javascript/worker" id="fileListWorker">
self.onmessage = function(e) {
// TODO: do something interesting with the files.
postMessage(e.data); // Pass through.
};
</script>
</head>
<body>
</body>
<input type="file" multiple>
<script>
document.querySelector('input[type="file"]').addEventListener('change', function(e) {
var files = this.files;
loadInlineWorker('#fileListWorker', function(worker) {
// Setup handler to process messages from the worker.
worker.onmessage = function(e) {
// Read each file aysnc. as an array buffer.
for (var i = 0, file; file = files[i]; ++i) {
var reader = new FileReader();
reader.onload = function(e) {
console.log(this.result); // this.result is the read file as an ArrayBuffer.
};
reader.onerror = function(e) {
console.log(e);
};
reader.readAsArrayBuffer(file);
}
};
worker.postMessage(files);
});
}, false);
function loadInlineWorker(selector, callback) {
window.URL = window.URL || window.webkitURL || null;
var script = document.querySelector(selector);
if (script.type === 'javascript/worker') {
var blob = new Blob([script.textContent]);
callback(new Worker(window.URL.createObjectURL(blob));
}
}
</script>
</html>
读取工作器中的文件
使用异步 FileReader
API 读取 worker 中的文件完全可以接受。不过,有一种更好的方法。在 Worker 中
同步 API (FileReaderSync
),可简化文件读取:
主应用:
<!DOCTYPE html>
<html>
<head>
<title>Using FileReaderSync Example</title>
<style>
#error { color: red; }
</style>
</head>
<body>
<input type="file" multiple />
<output id="error"></output>
<script>
var worker = new Worker('worker.js');
worker.onmessage = function(e) {
console.log(e.data); // e.data should be an array of ArrayBuffers.
};
worker.onerror = function(e) {
document.querySelector('#error').textContent = [
'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message].join('');
};
document.querySelector('input[type="file"]').addEventListener('change', function(e) {
worker.postMessage(this.files);
}, false);
</script>
</body>
</html>
worker.js
self.addEventListener('message', function(e) {
var files = e.data;
var buffers = [];
// Read each file synchronously as an ArrayBuffer and
// stash it in a global array to return to the main app.
[].forEach.call(files, function(file) {
var reader = new FileReaderSync();
buffers.push(reader.readAsArrayBuffer(file));
});
postMessage(buffers);
}, false);
与同步 FileReader
一样,回调已消失。这样可以简化
读取文件时的回调嵌套量。相反,readAs* 方法
返回读取文件。
示例:提取所有条目
在某些情况下,同步 API 对于某些任务要干净得多。回调较少 很好,确实可以提高内容的可读性。使用 同步 API 源自 Worker 的局限性。
出于安全考虑,发起调用的应用与 Web Worker 线程之间的数据
从未共享。调用 postMessage()
时,数据始终会复制到 worker 或从 worker 复制。
因此,并非所有类型的数据都可以传递。
很遗憾,FileEntrySync
和DirectoryEntrySync
目前并未下降
转换为可接受的类型。那么,如何将条目返回给发起调用的应用呢?
绕过此限制的一种方法是返回 filesystem: 网址s 列表,而不是条目列表。filesystem:
网址只是字符串,
因此它们非常容易传递此外,还可以将它们解析为
使用 resolveLocalFileSystemURL()
在主应用中创建条目。再度订阅
添加到 FileEntrySync
/DirectoryEntrySync
对象中。
主应用:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>Listing filesystem entries using the synchronous API</title>
</head>
<body>
<script>
window.resolveLocalFileSystemURL = window.resolveLocalFileSystemURL ||
window.webkitResolveLocalFileSystemURL;
var worker = new Worker('worker.js');
worker.onmessage = function(e) {
var urls = e.data.entries;
urls.forEach(function(url, i) {
window.resolveLocalFileSystemURL(url, function(fileEntry) {
console.log(fileEntry.name); // Print out file's name.
});
});
};
worker.postMessage({'cmd': 'list'});
</script>
</body>
</html>
worker.js
self.requestFileSystemSync = self.webkitRequestFileSystemSync ||
self.requestFileSystemSync;
var paths = []; // Global to hold the list of entry filesystem URLs.
function getAllEntries(dirReader) {
var entries = dirReader.readEntries();
for (var i = 0, entry; entry = entries[i]; ++i) {
paths.push(entry.toURL()); // Stash this entry's filesystem: URL.
// If this is a directory, we have more traversing to do.
if (entry.isDirectory) {
getAllEntries(entry.createReader());
}
}
}
function onError(e) {
postMessage('ERROR: ' + e.toString()); // Forward the error to main app.
}
self.onmessage = function(e) {
var data = e.data;
// Ignore everything else except our 'list' command.
if (!data.cmd || data.cmd != 'list') {
return;
}
try {
var fs = requestFileSystemSync(TEMPORARY, 1024*1024 /*1MB*/);
getAllEntries(fs.root.createReader());
self.postMessage({entries: paths});
} catch (e) {
onError(e);
}
};
示例:使用 XHR2 下载文件
Worker 的一个常见用例是使用 XHR2 下载大量文件, 并将这些文件写入 HTML5 FileSystem。对于工作器线程来说,这是完美的任务!
以下示例仅提取和写入一个文件,但您可以 以下载一组文件。
主应用:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>Download files using a XHR2, a Worker, and saving to filesystem</title>
</head>
<body>
<script>
var worker = new Worker('downloader.js');
worker.onmessage = function(e) {
console.log(e.data);
};
worker.postMessage({fileName: 'GoogleLogo',
url: 'googlelogo.png', type: 'image/png'});
</script>
</body>
</html>
downloader.js:
self.requestFileSystemSync = self.webkitRequestFileSystemSync ||
self.requestFileSystemSync;
function makeRequest(url) {
try {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, false); // Note: synchronous
xhr.responseType = 'arraybuffer';
xhr.send();
return xhr.response;
} catch(e) {
return "XHR Error " + e.toString();
}
}
function onError(e) {
postMessage('ERROR: ' + e.toString());
}
onmessage = function(e) {
var data = e.data;
// Make sure we have the right parameters.
if (!data.fileName || !data.url || !data.type) {
return;
}
try {
var fs = requestFileSystemSync(TEMPORARY, 1024 * 1024 /*1MB*/);
postMessage('Got file system.');
var fileEntry = fs.root.getFile(data.fileName, {create: true});
postMessage('Got file entry.');
var arrayBuffer = makeRequest(data.url);
var blob = new Blob([new Uint8Array(arrayBuffer)], {type: data.type});
try {
postMessage('Begin writing');
fileEntry.createWriter().write(blob);
postMessage('Writing complete');
postMessage(fileEntry.toURL());
} catch (e) {
onError(e);
}
} catch (e) {
onError(e);
}
};
总结
Web Worker 是一种未得到充分利用但未被充分利用的功能 。与我交流的大多数开发者都不需要这种额外的计算优势, 但它们不仅可用于单纯的计算, 如果您持怀疑态度(就像我一样),希望本文对您改变了主意有所帮助。 将磁盘操作(文件系统 API 调用)或 HTTP 请求等内容分流到工作器 非常适合,还有助于划分代码。HTML5 File API “Inside the Workers”为许多用户尚未探索过的 Web 应用提供了全新的酷炫功能。