如果说事件驱动体现了 Node.js 的编程风格,那么流则体现了 Node.js 处理数据的方式。很多人会用文件流,却没有真正理解“为什么要流式处理”和“背压”到底在解决什么问题。这一篇先把 Readable、Writable 和背压心智讲透。
为什么不总是把数据一次性读进内存?
最直接的原因当然是数据可能很大。一个小文本文件你一次性读进来问题不大,但如果对象换成大日志、视频片段、长时间网络传输或者持续输出的数据源,那么“一口气全读完再处理”的策略就会非常浪费内存,甚至根本不可行。
流式处理的价值就在于:数据不是必须先完整到达,才能开始消费。它可以边来边处理,边处理边往下游传递。这意味着:
- 内存占用更可控;
- 结果可以更早开始产生;
- 多阶段处理可以更自然地串联起来。
所以,流不是“更酷的一种读写方式”,而是一种面向持续数据的组织模型。
Readable 和 Writable 各自扮演什么角色?
你可以先把它们理解成两种方向不同的能力:
Readable负责“把数据源吐出来”;Writable负责“把收到的数据吃进去”。
文件读取、网络响应、命令输出这类场景,通常更接近可读流;文件写入、网络请求体、日志落盘这类场景,则更接近可写流。
重要的不是 API 细节,而是你要意识到:在流模型里,数据的流动方向本身就是系统设计的一部分。谁在生产、谁在消费、谁需要等待谁,这些关系都比“有没有一个大对象可用”更重要。
背压为什么是流真正关键的地方?
很多人第一次学流,只注意到“可以边读边写”,但真正让流模型成立的,是背压。你可以把背压理解成:当上游生产速度快于下游消费速度时,系统必须有能力把这种速度差管理起来,而不是任由数据无限堆积。
如果没有背压,会发生什么?
- 上游不停地产出数据;
- 下游来不及消费;
- 数据缓冲区持续增大;
- 内存和系统压力不断上升。
所以,背压本质上是在解决生产速度和消费速度不一致的问题。它让流不只是“持续传输数据”,而是“在可控节奏下持续传输数据”。
流使用中最常见的误区
流之所以容易被误用,是因为很多人一边用流,一边又在心智上退回到“先全量拿到再处理”的模式。最常见的误区包括:
- 一边读流,一边把所有数据又重新拼成一个大字符串;
- 忽略错误事件,只关心成功路径;
- 忘记处理结束时机,导致资源和状态没有正确收尾;
- 在不需要流的场景里硬用流,反而让代码更复杂。
所以,使用流时一个很重要的判断是:你到底是真的在做流式处理,还是只是把普通数据处理包了一层流外壳。
总结
这一篇我们先把流的上半部分心智搭起来了:为什么需要流式处理、Readable 和 Writable 分别负责什么,以及背压为什么是流模型真正能站得住的关键。
下一篇我们会继续往下走到 Transform、pipeline 和多段流组合,把流真正带进工程实践。