fetch返回的response.body是readablestream而非直接数据,需用getreader()或text()/json()等方法消费,且只能单次消费、不可重复读取。

fetch() 返回的 response.body 确实是 ReadableStream,但不能直接 .read() 或遍历
浏览器中 fetch() 的 response.body 是一个 ReadableStream,但它默认处于“locked”状态——只要没调用 .getReader() 或没转成其他消费形式(如 .text()、.json()),你就无法重复读取;一旦被消费过,再访问 body 就会报 TypeError: Failed to execute 'clone' on 'Response': Response body is already used。
常见误操作:在 await response.text() 之后还试图从 response.body 拿流,或对同一个 response 多次调用 .body.getReader()。
- 必须在未消费前就决定是否走流式路径,且只能选一种方式消费(要么全流式,要么全一次性)
-
response.body不是 Node.js 的Readable,没有.on('data')或.pipe() - 流式读取只适用于支持分块传输(chunked encoding)或大响应体场景,小响应用
.json()更简单可靠
用 getReader() + while 循环手动读取 chunk
这是最基础也最可控的流式读取方式。你需要显式获取 reader,然后循环调用 reader.read(),直到 done === true。
注意:每次 reader.read() 返回的是 Promise,value 是 Uint8Array,不是字符串——要转文本得自己用 TextDecoder 解码。
const response = await fetch('/api/stream');
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
console.log(chunk); // 可能是不完整的 UTF-8 字节序列,需 stream: true
}
reader.releaseLock();
-
TextDecoder的{ stream: true }很关键,否则遇到跨 chunk 的 UTF-8 字符会乱码 - 别忘了
reader.releaseLock(),否则后续无法再创建新 reader(哪怕只是想 clone) - 如果中途想中止,调用
reader.cancel(),它会触发服务器关闭连接(取决于协议和服务器实现)
用 async iterator(for await...of)更简洁,但兼容性需留意
现代浏览器(Chrome 63+、Firefox 65+、Safari 16.4+)支持直接 for await...of 遍历 response.body,底层自动调用 getReader() 并管理 lock。
const response = await fetch('/api/stream');
for await (const chunk of response.body) {
const text = new TextDecoder().decode(chunk);
console.log(text);
}
- 无需手动
getReader()或releaseLock(),语法糖更干净 - 但不支持 IE 和旧版 Safari;若需兼容,必须回退到
getReader()方式 - 每个
chunk仍是Uint8Array,仍需TextDecoder,且同样建议传{ stream: true }(虽然 for-await 内部已处理部分边界,但解码逻辑不变)
流式读取时如何处理 JSON 分块响应(如 Server-Sent Events 或 NDJSON)
纯 JSON 不适合直接流式解析,因为 { ... } 是整体结构。常见做法是服务端改用换行分隔的 JSON(NDJSON)或 SSE 格式,前端按行或按事件解析。
例如服务端返回每行一个 JSON 对象:
{"id":1,"msg":"hello"}
{"id":2,"msg":"world"}
前端可边收边 parse:
const response = await fetch('/api/ndjson');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done && !buffer) break;
if (value) {
buffer += decoder.decode(value, { stream: true });
}
const lines = buffer.split('\n');
buffer = lines.pop() || ''; // 保留不完整行
for (const line of lines) {
if (line.trim()) {
try {
const obj = JSON.parse(line);
console.log(obj);
} catch (e) {
console.warn('Invalid JSON line:', line);
}
}
}
}
reader.releaseLock();
- 流式 JSON 解析的核心是缓冲 + 行切分 + 增量 parse,不能指望
JSON.parse()处理不完整输入 - 服务端必须保证每条记录以
\n结尾,且不嵌套换行(NDJSON 规范) - 如果服务端发的是 SSE(
event: message\ndata: {...}\n\n),就得按event:/data:规则解析,不能简单 split('\n')
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











