TransformStream 不能直接压缩或解密数据,它仅提供可读流到可写流的管道接口,实际处理需在 transform() 中手动集成 pako 或 crypto.subtle 等方案。

TransformStream 能不能直接压缩或解密数据
不能。TransformStream 本身只是提供一个可读流 → 可写流的管道接口,不内置任何压缩或加密逻辑。它只是“搬运工”,真正干活的是你传进去的 transform() 方法里写的处理逻辑。常见误区是以为构造时传个参数就能自动 zip 或 AES,结果发现数据原样透传——因为没写实际处理代码。
浏览器环境也没有原生 CompressionStream(Chrome 120+ 才开始实验性支持且仅限 gzip/deflate),更没有 DecryptionStream 这种东西。所以必须手动集成 zlib-sync、pako 或 crypto.subtle 等方案,并把它们塞进 TransformStream 的生命周期里。
用 pako 实现流式 gzip 压缩(兼容性好)
pako 是目前前端最稳妥的 zlib 实现,支持分块压缩,但注意:它默认是全量压缩,要流式处理得手动调用 pako.deflate() 的增量模式(chunk + flush)。
- 每块输入需先
new Uint8Array()转为字节数组,再喂给pako.deflate()的push()方法(不是直接传ArrayBuffer) - 必须在
transform()中维护一个pako.Deflate实例,不能每次新建——否则压缩字典丢失,压缩率暴跌 - 流结束时务必调用
deflate.push([], pako.Z_FINISH),否则末尾数据被截断 - 压缩后输出是
Uint8Array,记得用controller.enqueue()推入下游,别忘了controller.desiredSize的背压提示
示例关键片段:
立即学习“前端免费学习笔记(深入)”;
const compressStream = new TransformStream({
transform(chunk, controller) {
if (!this.deflate) {
this.deflate = new pako.Deflate({ level: 6 });
}
this.deflate.push(chunk, false);
while (this.deflate.chunks.length) {
controller.enqueue(this.deflate.chunks.shift());
}
},
flush(controller) {
this.deflate.push(new Uint8Array(0), true);
while (this.deflate.chunks.length) {
controller.enqueue(this.deflate.chunks.shift());
}
}
});
用 crypto.subtle 实现 AES-GCM 流式解密
浏览器 crypto.subtle 不支持真正的流式解密(比如边收边解),但可以模拟:把输入按 16 字节对齐分块,缓存未对齐部分,等凑够完整块再调用 decrypt()。GCM 模式还必须严格处理 IV 和认证标签(tag)——标签只能在最后出现一次,不能拆开。
- IV 必须在流开头单独传入(比如前 12 字节),不能混在数据流里动态解析
- 解密前需先用
crypto.subtle.importKey()导入密钥,且密钥必须标记extractable: false,否则后续无法复用 - 每块解密需等待
crypto.subtle.decrypt()Promise 完成,不能并发多块——GCM 是串行状态机,乱序会失败 - 最后一块必须包含认证标签(通常最后 16 字节),调用
decrypt()时传入完整{ name: 'AES-GCM', iv, tag }选项
错误现象:DOMException: The operation failed for an operation-specific reason —— 大概率是 IV 长度不对(AES-GCM 要求 12 字节)、或 tag 位置/长度错、或密钥没正确导入。
组合压缩与解密时最容易忽略的顺序和边界
如果想先解密再解压(常见于服务端加密+压缩的响应),千万别把两个 TransformStream 直接 .pipeThrough() 串联了事。问题在于:解密流输出的是原始字节,但 pako.Inflate 需要的是完整的 gzip header + body,而流式解密可能把 header 拆到两个 chunk 里。
- 必须在解密
transform()中做 buffer 合并:缓存未完成的 gzip header(前 10 字节),直到收到足够字节才启动pako.Inflate -
TransformStream的desiredSize对 crypto 操作无效——它只影响背压信号,不控制实际处理时机 - 错误堆栈里看到
RangeError: invalid input length或pako: incorrect header check,基本就是 header 被截断或解密后字节错位 - 移动端 Safari 对大块
ArrayBuffer的 GC 更敏感,建议单次enqueue()不超过 64KB,避免卡顿
真实场景中,header 解析、IV 提取、tag 校验、gzip 同步点这几个边界条件,比算法本身更容易出问题。

















