done 是流结束的唯一权威信号,value 仅在 done 为 false 时有效;for-await-of 隐式处理 done,手动 read 需显式判断;误判常见于用 value.length 或超时判断结束。

识别 done 与 value 的状态解构,关键在于理解流式遍历中“数据是否结束”和“当前是否有有效数据”这两个信号的分工。它们不是并列条件,而是有明确主次:done 是终止判定的唯一权威依据,value 只在 done 为 false 时才可信。
done 为 true 是唯一可靠的终止信号
ReadableStream 的 reader.read() 每次返回一个 Promise,其 resolve 结果必含 done(布尔)和 value(Uint8Array 或 undefined)。只要 done === true,就代表整个流已彻底结束,后续不会再有任何数据块。此时 value 必定为 undefined,强行读取或使用它会出错。这个判断不依赖于内容长度、空数据或超时,只取决于底层流是否发出“完结帧”。
- 不要用
value?.length === 0判断结束——空块(如心跳包、空响应体)合法且常见 - 不要等超时或重试次数——流可能长时间无新块但未结束(如长连接推送)
- 一旦
done为 true,必须退出循环或停止调用read()
value 仅在 done 为 false 时有意义
当 done === false,value 才是本次读取到的真实数据块,类型为 Uint8Array。它可能为空(value.length === 0),但这只是该批次无数据,并不表示流结束。你应将其追加到累积缓冲区(如 responseText += new TextDecoder().decode(value)),然后继续下一轮 reader.read()。
- 忽略 value 是否为空,只关注 done 状态来决定流程走向
- 对 value 的处理(解码、拼接、解析)必须包裹在
if (!done) { ... }内部 - 若需区分“空块”和“流结束”,只靠 done 就够了:前者是
!done && value.length === 0,后者是done && value === undefined
在 for-await-of 中,done 隐式控制循环退出
使用 for await (const chunk of stream) 时,引擎自动处理 done:每次迭代前调用 next(),若返回结果中 done === true,循环立即终止,不会进入本轮循环体。此时你根本拿不到 value,也无需手动检查 done。
- 适合简单消费场景,代码简洁,不易出错
- 无法在 done 为 true 时同步获取 abortController 或执行 onComplete 回调——这些需在显式
reader.read()循环中完成 - 若需精细控制(如中断、错误注入、中间状态上报),仍要退回到手动 read + done 判断模式
常见误判边界与应对
实际开发中容易混淆的几个临界点:
- 网络中断 vs 流正常结束:done 为 true 是协议层确认结束;若 read() 抛异常(如 TypeError: ReadableStream is locked),才是异常中断,需走 error 分支
- 服务端提前关闭 vs 客户端 abort:abortController.abort() 会令后续 read() 立即 reject,不会等到 done;done 为 true 一定是服务端主动 EOF
- 文本流末尾换行符:最后一块 value 可能含 \n 或不含,这与 done 无关;拼接时统一处理即可,勿据此判断是否终了










