适用于辅助角色的同步文件系统 API
HTML5 文件系统 API 和 Web Workers 本身就非常强大。文件系统 API 最终为 Web 应用程序带来了分层存储和文件 I/O,而 Workers 为 JavaScript 带来了真正的异步“多线程”。但是,当您一起使用这些 API 时,您可以构建一些真正有趣的应用程序。
本教程提供了利用 Web Worker 内部 HTML5 文件系统的指南和代码示例。它假定具有两个 API 的工作知识。如果您还没有准备好深入了解或有兴趣了解有关这些 API 的更多信息,请阅读两个讨论基础知识的精彩教程: 探索文件系统 API 和 Web Worker 的基础知识 。
同步与异步 API
异步 JavaScript API 可能很难使用。它们很大。它们很复杂。但最令人沮丧的是,它们为事情出错提供了很多机会。您要处理的最后一件事是在已经异步的世界(Workers)中对复杂的异步API(文件系统)进行分层!好消息是, 文件系统 API 定义了一个同步版本来减轻 Web Worker 的痛苦。
在大多数情况下,同步 API 与其异步表亲完全相同。方法、属性、特性和功能将很熟悉。主要偏差是:
- 同步 API 只能在 Web Worker 上下文中使用,而异步 API 可以在 Worker 上下文中使用,也可以在 Worker 外部使用。
- 回调已出。API 方法现在返回值。
- 窗口对象 (
requestFileSystem()
和resolveLocalFileSystemURL()
) 成为requestFileSystemSync()
和resolveLocalFileSystemSyncURL()
. 注意: 这些方法是工作人员全局范围的成员,而不是window
对象。
除了这些例外,API 是相同的。好的,我们很好!
请求文件系统
Web 应用程序通过请求 LocalFileSystemSync
Web 辅助角色中的对象。这 requestFileSystemSync()
暴露在辅助角色的全局范围内:
var fs = requestFileSystemSync(TEMPORARY, 1024*1024 /*1MB*/);
请注意新的返回值,现在我们使用的是同步 API,并且没有成功和错误回调。
与普通的文件系统 API 一样,方法目前带有前缀:
self.requestFileSystemSync = self.webkitRequestFileSystemSync ||
self.requestFileSystemSync;
处理配额
目前,无法 请求 PERSISTENT
辅助角色上下文中的配额。我建议在worker之外处理配额问题。该过程可能如下所示:
- worker.js:将任何文件系统 API 代码包装在
try/catch
所以任何QUOTA_EXCEED_ERR
捕获错误。 - worker.js:如果你抓住一个
QUOTA_EXCEED_ERR
,发送postMessage('get me more quota')
返回主应用程序。 - 主应用:通过
window.webkitStorageInfo.requestQuota()
收到 #2 时跳舞。 - 主应用:用户授予更多配额后,发送
postMessage('resume writes')
返回给工作人员以告知其额外的存储空间。
这是一个相当复杂的解决方法,但它应该有效。有关 requesting quota 使用 PERSISTENT
使用文件系统 API 进行存储。
使用文件和目录
的同步版本 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 代码的一般复杂性,您很快就会感到沮丧。可以简化生活的一件事是将所有相关的 Worker 代码包装在 try/catch 中。然后,如果发生任何错误,请使用以下命令将错误转发到主应用程序 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 和 ArrayBuffers
当 Web Workers 第一次出现时,他们只允许发送字符串数据。 postMessage()
.后来,浏览器开始接受可序列化的数据,这意味着可以传递 JSON 对象。然而,最近,像Chrome这样的一些浏览器接受更复杂的数据类型来传递。 postMessage()
使用 结构化克隆算法 。
这到底意味着什么?这意味着在主应用程序和 Worker 线程之间传递二进制数据要容易得多。支持工作线程结构化克隆的浏览器允许您传递类型化数组, ArrayBuffer
s, File
s,或 Blob
s进入worker。尽管数据仍然是副本,但能够通过 File
意味着与前一种方法相比具有性能优势,前一种方法涉及在将文件传递到 postMessage()
.
下面的示例将用户选择的文件列表传递给专用辅助角色。Worker 只需遍历文件列表(简单显示返回的数据实际上是一个 FileList
),主应用程序将每个文件读取为 ArrayBuffer
.
此示例还使用 的改进 中所述 内联 Web 辅助角色技术在 Web 辅助角色基础知识 版本。
<!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">
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。但是,有更好的方法。在工作线程中,有一个同步 API ( FileReaderSync
) 简化了读取文件的过程:
主应用:
<!DOCTYPE html>
<html>
<head>
<title>Using FileReaderSync Example</title>
<style>
#error { color: red; }
</style>
</head>
<body>
<input type="file" multiple />
<output></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 辅助角色线程之间的数据永远不会共享。在以下情况下,数据始终复制到工作线程和从工作线程复制数据 postMessage()
被称为。因此,并非每种数据类型都可以传递。
不幸 FileEntrySync
和 DirectoryEntrySync
当前不属于可接受的类型。那么,如何将条目恢复到调用应用程序呢?规避此限制的一种方法是返回 文件系统列表:URL ,而不是条目列表。 filesystem:
URL 只是字符串,因此它们非常容易传递。此外,可以使用以下命令将它们解析为主应用程序中的条目 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 下载文件
Workers的一个常见用例是使用 XHR2 下载一堆文件,并将这些文件写入HTML5文件系统。对于工作线程来说,这是一项完美的任务!
以下示例仅提取和写入一个文件,但您可以映像扩展该文件以下载一组文件。
主应用:
<!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>
下载者.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 Workers 是 HTML5 中未被充分利用和未被充分重视的功能。与我交谈过的大多数开发人员不需要额外的计算优势,但它们不仅可以用于纯计算。如果你持怀疑态度(就像我一样),我希望这篇文章能帮助你改变主意。将磁盘操作(文件系统 API 调用)或 HTTP 请求卸载到 Worker 等内容是很自然的,也有助于划分代码。Workers内部的HTML5文件API为Web应用程序打开了一个全新的令人敬畏的功能,这是很多人没有探索过的。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论