先记住这个答案
flowing 模式下,可读流通过 data 事件持续向消费者交付数据;paused 模式下,消费者通常通过 read 主动取出缓冲内容,并用 readable 得知有可读进展。readableFlowing 还区分初始未选择消费机制的 null、暂停自动流动的 false 和正在流动的 true。添加 data 监听、pipe、resume 等操作会影响模式,但已有暂停或 readable 消费方式也会影响切换,不能把每个操作简化为无条件开关。一次使用应选定清楚的消费风格。
- data 偏向自动交付,readable 配合主动 read
- readableFlowing 有 null、false、true 三种状态
- 不要无目的混用多个消费接口
readable 通知之后仍要主动取数据
readable 不是携带业务 chunk 的回调,它提示流有可读进展。消费者调用 read 取得数据,并在暂时取不到时停止本轮读取,等待下一次通知;不能把每次 readable 当作恰好一条业务记录。
下面使用对象模式来源中的三个字符串,收到通知后循环 read,直到返回 null。代码没有再注册 data 消费器,因而不会把同一段读取逻辑分散到两个入口,使状态和数据归属难以推理。
import { Readable } from 'node:stream';
export function readManually(): Promise<string[]> {
const source = Readable.from(['A', 'B', 'C']);
const values: string[] = [];
return new Promise((resolve, reject) => {
source.once('error', reject);
source.on('readable', () => {
let value: unknown;
while ((value = source.read()) !== null) values.push(String(value));
});
source.once('end', () => resolve(values));
});
}Readable.from 默认按对象模式处理这里的数组元素,测试应得到三个完整字符串。换成字节流后,chunk 边界不保证等于业务记录边界,需要另加分帧和编码处理。
pause 与 resume 影响的是交付方式
在流动模式调用 pause 会停止继续通过 data 自动交付,但不等于所有底层生产和缓冲立即归零。恢复时应调用与所选消费方式匹配的操作,并观察是否有下游背压或其他控制因素。
移除 data 监听器也不应当作可靠的暂停指令;在没有消费者时盲目 resume 还可能把数据读出并丢弃。需要保留数据的逻辑应明确什么时候开始消费,而不是先启动流动再等待未来某处注册处理器。
不要用混合接口解释不稳定现象
同一个来源同时使用 readable、data、pipe 或异步迭代,会增加谁在消费数据的歧义。官方建议选择一种主要风格;如果需要复制到多个目的地,应显式设计分发与背压,而不是随意叠加读取入口。
手动 data 处理器中启动异步任务也不会让流自动等待该 Promise。若每个 chunk 都发起异步写入而不限制并发,可能在下游很慢时积累大量任务,需要用合适的流转换或异步消费控制节奏。
容易答错的地方
- 认为 async data 回调会自动提供背压
- EventEmitter 不会因为监听器返回 Promise 就自动等待它完成。持续到来的 chunk 可能触发很多并发工作,应显式暂停恢复或采用能表达下游完成的管道和迭代方案,并处理异步失败。
- 把 paused 理解成数据已经丢弃
- 暂停通常保留了当前缓冲和后续读取可能性,是否丢数据取决于实际消费方式和来源。应区分暂停推送、销毁流和无监听器时主动恢复,不能把这些操作当作同义词。
面试官还会怎么问?
readableFlowing 为 null 和 false 有什么区别?
null 常表示尚未建立相应消费机制,false 则表示自动流动被停止或采用主动读取等状态。只用布尔判断会抹掉这层差异,调试时应结合监听器、pipe 和 pause 操作记录解释。
read 返回 null 就表示结束了吗?
不一定,可能只是当前没有足够数据。需要通过 end、readableEnded 或合适完成工具判断结束,并继续等待下一次可读通知;把暂时无数据当成结束会截断后续内容。
使用异步迭代还要手动监听 data 吗?
通常不需要。异步迭代已经提供消费入口,再注册 data 会引入额外行为和理解负担。应围绕一种风格处理数据、错误与取消,并明确提前退出迭代时流如何被关闭。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。