Web Streams 与增量文本解码
解释 ReadableStream 如何逐块提供字节,以及 TextDecoder 和文本缓冲区如何处理跨块字符与不完整记录。
#type / concept
#status / evergreen
#tech / dev / frontend
#resource / javascript
#platform / browser
[!info] related notes
- 流式协议:NDJSON、SSE 与流式协议边界
- 取消:AbortController 与异步取消
- 项目:BodySense MOC
Web Streams 与增量文本解码
一句话定义
Web Streams API 让消费者逐块读取响应字节;增量文本解码负责把任意分块的 Uint8Array 安全还原为字符和完整记录。
const reader = response.body?.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (reader) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
}
buffer += decoder.decode(); // 冲刷解码器内部剩余字节
两种边界不能混淆
网络分块边界不等于:
- UTF-8 字符边界
- 文本换行边界
- JSON 对象边界
- SSE 事件边界
一个中文字符可能跨两个字节块,一行 JSON 也可能跨多个块。因此必须先增量解码,再用缓冲区寻找协议规定的完整记录。
stream: true
TextDecoder.decode(value, { stream: true }) 告诉解码器后面还有字节。若块末尾只有某个多字节字符的一部分,解码器会先保留它,等待下一块,而不是产生替换字符。
流结束时调用一次无参数 decoder.decode(),用于冲刷内部剩余状态。
缓冲区模式
buffer += decoded;
const lines = buffer.split('\n');
buffer = lines.pop() ?? '';
for (const line of lines) processLine(line);
最后一段可能是不完整行,必须留给下一次读取。协议如果允许 \r\n,处理行时还要规范化尾部的 \r。
资源与错误边界
- 响应 body 可能为
null。 - 读取可能因网络、取消或服务端断开而失败。
- 不再读取时应释放 reader 或取消请求。
- 测试应故意把 Unicode 字符和 JSON 行切在不同位置。