问题描述
我有一个在浏览器中运行的普通JS脚本(Chrome,并且仅需支持Chrome-如果对浏览器的支持非常重要)。
我想将15 MB的gzip文件卸载到Web Worker并异步解压缩该文件,然后将未压缩的数据返回到主线程,以免在解压缩过程中冻结主应用程序线程。
在主线程中解压缩时,我使用的是JSXCompressor library,效果很好。但是,由于此库引用了无法从工作程序上下文访问的window对象,因此我无法使用注入到工作程序代码中的同一库(运行解压缩会在库的第一行中引发一个例外,其中提到“窗口” “,说这是未定义的。)
对于在一个下午的Google搜索中设法挖掘的其他JS库,例如zlib或更现代的Pako,也是如此。它们似乎都以一种或另一种方式引用DOM元素,当在Web Worker上下文中使用DOM元素时会引发异常。
所以我的问题是-是否有人知道我可以做到这一点的方法,可以通过黑客向我解释我似乎错了的地方,或者通过向我提供指向可以此用例中的功能(我只需要解压缩,标准gzip)?
编辑:我也对任何可以利用内置浏览器功能进行ungzip压缩的黑客都很感兴趣,就像HTTP请求一样。
谢谢。
解决方法
有一个新的Web API Compression streams提案,已在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
用于字符串转换。
P.S。实际上,这仍然与我的测试中的本机CompressionStream
解决方案一样快,但是可以在更多浏览器中使用。如果需要流支持,请使用fflate的AsyncGunzip
流类。