我在浏览器中运行了一个普通的JS脚本(Chrome,如果浏览器支持很重要的话,只需要支持Chrome(。
我想将一个15 MB的gzip文件卸载到web工作程序,并异步解压缩该文件,然后将未压缩的数据返回到主线程,以免在解压缩过程中冻结主应用程序线程。
在主线程中解压缩时,我使用JSXCompressor库,这很好。然而,由于这个库引用了window对象,而这个对象不能从worker上下文访问,所以我不能使用注入到worker代码中的同一个库(运行解压缩会在库的第一行引发一个异常,提到"window",说它是未定义的(。
我在一个下午的谷歌搜索中找到的其他JS库也是如此,比如zlib或更现代的Pako。它们似乎都以某种方式引用了DOM元素,当在web工作者上下文中使用时会引发异常。
所以我的问题是,有人知道我可以做到这一点的方法吗?要么通过黑客向我解释我似乎做错了什么,要么通过向我提供一个JS库的链接,该库可以在这个用例中运行(我只需要解压缩,标准gzip(?
编辑:我还对任何可以利用内置浏览器功能进行解压缩的黑客攻击感兴趣,就像对HTTP请求所做的那样。
非常感谢。
有一个新的web API压缩流方案,该方案已经在Chrome中实现,它正是这样做的:异步压缩/解压缩数据。
它应该同时支持deflate和gzip算法,并且应该使用本机实现->比任何lib都快。
因此,在Chrome中,您可以简单地执行以下操作:
if( "CompressionStream" in window ) {
(async () => {
// To be able to pass gzipped data in stacksnippet we host it as a data URI
// that we do convert to a Blob.
// The original file is an utf-8 text "Hello world"
// which is way bigger once compressed, but that's an other story ;)
const compressed_blob = await fetch("data:application/octet-stream;base64,H4sIAAAAAAAAE/NIzcnJVyjPL8pJAQBSntaLCwAAAA==")
.then((r) => r.blob());
const decompressor = new DecompressionStream("gzip");
const decompression_stream = compressed_blob.stream().pipeThrough(decompressor);
const decompressed_blob = await new Response(decompression_stream).blob();
console.log("decompressed:", await decompressed_blob.text());
})().catch(console.error);
}
else {
console.error("Your browser doesn't support the Compression API");
}
显然,这在Web Workers中也是可用的,但由于API被设计为完全异步的,并且使用Streams,浏览器理论上应该已经能够将其他线程上的所有艰苦工作外包给自己。
现在,这仍然是一个未来的解决方案,对于今天来说,您可能仍然想使用库。
然而,我们在这里不推荐库,但我应该注意的是,我个人每天都在Web Workers中使用pako,没有问题,我不明白为什么压缩库会需要DOM,所以我怀疑你做错了什么™。
(async() => {
const worker_script = `
importScripts("https://cdnjs.cloudflare.com/ajax/libs/pako/1.0.11/pako_inflate.min.js");
self.onmessage = async (evt) => {
const file = evt.data;
const buf = await file.arrayBuffer();
const decompressed = pako.inflate(buf);
// zero copy
self.postMessage(decompressed, [decompressed.buffer]);
};
`;
const worker_blob = new Blob([worker_script], { type: "application/javascript" });
const worker_url = URL.createObjectURL(worker_blob);
const worker = new Worker(worker_url);
const compressed_blob = await fetch("data:application/octet-stream;base64,H4sIAAAAAAAAE/NIzcnJVyjPL8pJAQBSntaLCwAAAA==")
.then((r) => r.blob());
worker.onmessage = ({ data }) => {
console.log("received from worker:", new TextDecoder().decode(data));
};
worker.postMessage(compressed_blob);
})().catch(console.error);
我编写了一个库fflate来完成这项任务。它提供了它支持的每个压缩/解压缩方法的异步版本,但该库不是在事件循环中运行,而是将处理委托给一个单独的线程。您不需要手动创建工作程序或指定包内部工作程序的路径,因为它会动态生成这些工作程序。
import { gunzip } from 'fflate';
// Let's suppose you got a File object (from, say, an input)
const reader = new FileReader();
reader.onloadend = () => {
const typedArrayUncompressed = new Uint8Array(reader.result);
gunzip(typedArrayUncompressed, (err, gzippedResult) => {
// This is a Uint8Array
console.log('Compressed output:', gzippedResult);
});
}
reader.readAsArrayBuffer(fileObject);
实际上,您需要将输入格式转换为Uint8Array,然后将输出格式转换为您想要使用的格式。例如,FileReader是最跨平台的文件解决方案,fflate.strToU8
和fflate.strFromU8
适用于字符串转换。
附言:这实际上仍然和我测试中的原生CompressionStream
解决方案一样快,但可以在更多浏览器中使用。如果您想要流媒体支持,请使用fflate的AsyncGunzip
流类。