先记住这个答案
fetch 返回的 Response 对象,其 body 是一个 ReadableStream,数据只能被消费一次。一旦调用 json()、text()、blob() 或直接读取 body,该流便会被锁定或关闭,bodyUsed 从 false 变为 true。再次读取会抛出 TypeError。需要读取两次时,必须在任何读取操作之前调用 response.clone(),得到一个共享底层流的副本,然后分别对原对象和副本各读一次,每次读取都会使对应的 bodyUsed 变为 true。
- Response body 是流,读取后即被消耗。
bodyUsed只反映是否已读取,不可重置。- 需读两次必须提前用
clone()复制。
底层流决定单次消费
fetch 响应体本质是一个 ReadableStream,数据按数据块(chunk)到达,消费时通过 reader 逐步读出。一旦执行 json()、text() 等方法,浏览器内部会将整个流读入缓冲区并释放对原始流的引用,此时 bodyUsed 被置为 true。这个布尔值不是开关,而是对状态的记录,表示 body 已不可再次访问。
如果强行再次调用读取方法,会触发 TypeError,因为流的内部控制器已经关闭或锁定了。设计这种机制是为了避免内存上的重复缓存,因为很多响应体很大(如视频、大 JSON),逐个缓存副本代价过高。同时又想支持两个消费者时,便提供了 clone():它仅在被克隆的对象尚未读取时有效,将流分叉成两个独立的内部流,但缓冲区的数据只有一份真实存储,两个副本各自读走自己那份。
网关同时校验与透传的取舍
在一个纯前端拦截器里,要统一检查 API 返回的业务状态:先读一次 JSON 判断 code 是否为 200,然后需要把原始响应继续传给调用方用于后续的 blob 下载。输入是同一个 response,如果直接 await response.json() 后再把 response 返回,调用方取到的 bodyUsed 已为 true,再想读就崩了。此时必须先 const cloned = response.clone(),用 cloned.json() 做校验,原响应保留给下游消费。
校验通过后,下游读取原始响应时 bodyUsed 仍为 false,完全合法。但要注意,克隆也占用了内存,不可能无限克隆;而且克隆必须在读取前,如果误把 cloned 后面的读取顺序调换,原响应也会被锁定。实践中可将这段逻辑封装成中间件,用 bodyUsed 作防御性检查,明确返回错误而不是运行时异常。
克隆时机与消耗成本
clone() 最大的前提是原始对象尚未读取。如果在 json() 之后调用,会立即抛出 TypeError。此外,即使克隆成功,每个克隆体都只能消费一次,不存在一个读完另一个还能读同一份的语义——它们各自消耗自己的流。对于大体积的二进制响应,克隆并不会复制数据体,但会复制流的内部状态,增加内存和 CPU 开销,因此不可滥用。
当需要读取两次且无法提前克隆(比如 response 已经流转过一次)时,唯一可靠的办法是先把内容读成 ArrayBuffer 或 Blob,再用 blob.slice() 或手动构造新 Response 来“复制”,但这等于在应用层额外缓存整个正文,只适合数据量可控的场景。判断能否克隆,直接看 response.bodyUsed,为 false 才安全。
容易答错的地方
- 认为 bodyUsed 可以重置
- 有些人试图通过给 response 重新赋值或调用某些方法把 bodyUsed 改回 false,这是不可能的。bodyUsed 是只读属性,由内部流状态决定,重置行为不存在。正确方式永远是克隆或提前缓存内容。
- 误以为 clone 后能读多次
- clone 产生的每个 Response 仍然只能读一次,clone 的目的是同时提供两个独立的消费通道,而不是让一个响应无限次读取。如果试图对已消费的克隆再次读取,同样会抛出异常。
面试官还会怎么问?
如果已经读了一次但还没读完,能 clone 吗?
不能。只要读取动作开始,bodyUsed 就变为 true,即使是通过 getReader() 读取但没读完,也认为 body 已被使用,clone 会抛错。
克隆后的响应会影响网络缓存吗?
不会。clone 只操作内存中的流,不会触发新的网络请求,与 HTTP 缓存无关。它只是复制了对相同响应数据的访问路径。
Service Worker 里经常 clone,这和普通页面有区别吗?
原理相同,但 Service Worker 中响应常需要转发给页面并同时本地缓存,因此 clone 是标准用法。要留意 clone 的时机必须在放入缓存和返回页面之前,且每个副本只能被消费一次。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。