gzip压缩下fetch()响应如何获取正确长度实现进度条
Fetch 实现gzip压缩资源下载进度的可行方案
核心结论
浏览器原生Fetch API暴露的response.body是自动完成gzip/deflate/br解压后的数据流,没有任何前端API可以获取网络层实际传输的压缩后字节数,因此「统计已下载压缩数据大小和content-length比对」的诉求无法在纯前端层面实现。
要实现准确的进度条,只能走「获取解压后数据总长度,和读取到的解压后字节数比对」的路径,落地方式如下:
落地方案
方案1:服务端返回自定义头标识解压后大小(兼容性100%,推荐优先使用)
服务端在生成压缩响应时,额外新增自定义响应头返回资源未压缩的真实字节长度,例如x-decompressed-content-length: 1228800(对应1200KB解压后大小)。
前端读取该头作为进度计算的总长度即可,跨域场景需要在服务端CORS配置的Access-Control-Expose-Headers中暴露该自定义头,否则前端无法读取。
修正后的代码示例:
fetch("http://example.com/bigdata.json") .then((response) => { if (!response.body) return; const reader = response.body.getReader(); let download_done = 0; // 优先读取解压后总长度,未开启压缩场景降级取默认content-length const download_size = parseInt( response.headers.get("x-decompressed-content-length") ?? response.headers.get("content-length") ); return new ReadableStream({ start(controller) { function push() { reader.read().then(({ done, value }) => { if (done) { controller.close(); download_size && !Number.isNaN(download_size) && report_progress(download_size, download_size); return; } if (value) { download_done += value.byteLength; if (download_size && !Number.isNaN(download_size)) { report_progress(download_done, download_size); } else { // 无有效总长度时,切换为不确定状态的加载样式 show_indeterminate_loading(); } } controller.enqueue(value); push(); }); } push(); }, }); }) .then((stream) => { return new Response(stream, { headers: { "Content-Type": "application/json" }, }).json(); }) .then(function (result) { console.groupCollapsed("api_request(bigdata.json)"); console.log(result); console.groupEnd(); })
方案2:构建阶段预埋资源大小映射(适合静态资源场景)
如果请求的是站点自身托管的静态资源,可以在项目构建阶段生成一份资源清单,记录每个静态资源压缩后的路径、对应的解压后实际大小。前端发起请求前先查清单拿到对应资源的解压后总长度,同样可以实现准确进度计算,不需要修改服务端响应逻辑。
避坑说明
- XMLHttpRequest的
progress事件存在完全相同的问题:total字段取content-length的压缩后大小,loaded字段在现代浏览器中返回解压后字节数,同样会出现进度超过100%的错误,无法用来解决该问题。 - 所有尝试在前端手动拦截压缩流、统计压缩字节数的方案均不可行:浏览器禁止前端修改
Accept-Encoding请求头,网络层的解压过程对上层JS完全黑盒,没有规范内的绕过方式。 - 如果既无法修改服务端配置,也拿不到资源的预计算大小,不要强行实现精确进度条,改用不确定状态的加载动效即可,避免出现进度超过100%的异常体验。
内容的提问来源于stack exchange,提问作者Shish
相关产品推荐
相关产品推荐

