fetch().body是现成的readablestream,无需new;它是原始字节流,须经textdecoderstream解码为字符串流才能用于html解析或显示。

fetch().body 就是现成的 ReadableStream,别自己 new
浏览器里最常用、也最该优先用的 ReadableStream 来源就是 fetch() 的 response.body。它天然就是个可读流,类型为 ReadableStream<uint8array></uint8array>,不需要手动调用 new ReadableStream() 构造——自己造容易漏掉背压控制、错误传播或 cancel 逻辑。
常见错误是拿到 response.body 后直接传给 DOMParser 或拼接成字符串:response.body 是原始字节流,不是字符串;不经过解码就 toString() 会得到一串逗号分隔的数字,比如 "239,187,191,228,184,150",根本不是 HTML。
- 必须先用
response.body.pipeThrough(new TextDecoderStream())转成字符串流(ReadableStream<string></string>) - 服务端没发
Content-Type: text/html; charset=utf-8也不影响,默认仍按 utf-8 解码;但若服务端用gb2312,得显式写new TextDecoderStream('gb2312') - 别用
new TextDecoder().decode(chunk)手动解码每块——它不支持{ stream: true },遇到跨 chunk 的中文(如一个汉字被切在两个Uint8Array中间)会丢字或抛错
getReader() 不是“开始读”,而是“拿到遥控器”
response.body.getReader() 返回的是一个 ReadableStreamDefaultReader,它本身不触发任何数据拉取,只是把流的控制权交给你——就像拿到空调遥控器,不按“开机”键,空调不会启动。
典型误用是调完 getReader() 就以为流自动跑了,结果卡住没反应。真正干活的是 reader.read(),它返回一个 Promise,resolve 后才拿到一块数据。
-
reader.read()每次只取一个Uint8Array块,大小由网络传输和底层决定,不是固定 64KB 或其他值 - 不能并发调用多个
read();必须等前一个 Promise settle(fulfilled 或 rejected)后,再调下一个 - 流结束时,
value是undefined,done是true;只靠done判断即可,不用额外检查value - 想安全循环读取,优先用
for await (const chunk of reader),它内部已处理done和异常;手动 while + await 容易在某些上下文(如 DevTools)中挂起
htmlparser2 是唯一靠谱的流式 HTML 解析器
DOMParser 必须等完整字符串才能 parse,不支持流;innerHTML 同理。想边收边解析 HTML,唯一可行路径是 TextDecoderStream → htmlparser2.Parser。
htmlparser2 的 Parser 构造函数接受回调,靠 write() 接收字符串 chunk,内部维护标签栈,能正确处理跨 chunk 的未闭合标签(比如 <div> 在第一块,<code>











