如何在JavaScript网络工作者中异步解压缩gzip文件?

问题描述

我有一个在浏览器中运行的普通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.strToU8fflate.strFromU8用于字符串转换。

P.S。实际上,这仍然与我的测试中的本机CompressionStream解决方案一样快,但是可以在更多浏览器中使用。如果需要流支持,请使用fflate的AsyncGunzip流类。