- stream 模块可以通过以下方式使用:
const stream = require('stream');
流可以是可读的、可写的、或者可读可写的。 所有的流都是 EventEmitter 的实例。
stream 模块本身主要用于开发者创建新类型的流实例。 对于以消费流对象为主的开发者,极少需要直接使用 stream 模块。
- 可写流(比如例子中的 res)会暴露了一些方法,比如 write() 和 end() 用于写入数据到流。
当数据可以从流读取时,可读流会使用 EventEmitter API 来通知应用程序。 从流读取数据的方式有很多种。
可写流和可读流都通过多种方式使用 EventEmitter API 来通讯流的当前状态。
Duplex 流和 Transform 流都是可写又可读的。
对于只需写入数据到流或从流消费数据的应用程序,并不需要直接实现流的接口,通常也不需要调用 require('stream')。
流的类型
Node.js 中有四种基本的流类型:
- Writable - 可写入数据的流(例如 fs.createWriteStream())。
- Readable - 可读取数据的流(例如 fs.createReadStream())。
- Duplex - 可读又可写的流(例如 net.Socket)。
- Transform - 在读写过程中可以修改或转换数据的 Duplex 流(例如 zlib.createDeflate())。
对象模式
Node.js 创建的流都是运作在字符串和 Buffer(或 Uint8Array)上。 当然,流的实现也可以使用其它类型的 JavaScript 值(除了 null)。 这些流会以“对象模式”进行操作。
当创建流时,可以使用 objectMode 选项把流实例切换到对象模式。 将已存在的流切换到对象模式是不安全的。
缓冲
可写流和可读流都会在内部的缓冲器中存储数据,可以分别使用的 writable.writableBuffer 或 readable.readableBuffer 来获取。
可缓冲的数据大小取决于传入流构造函数的 highWaterMark 选项。 对于普通的流,highWaterMark 指定了字节的总数。 对于对象模式的流,highWaterMark 指定了对象的总数。
当调用 stream.push(chunk) 时,数据会被缓冲在可读流中。 如果流的消费者没有调用 stream.read(),则数据会保留在内部队列中直到被消费。
一旦内部的可读缓冲的总大小达到 highWaterMark 指定的阈值时,流会暂时停止从底层资源读取数据,直到当前缓冲的数据被消费 (也就是说,流会停止调用内部的用于填充可读缓冲的 readable._read())。
当调用 writable.write(chunk) 时,数据会被缓冲在可写流中。 当内部的可写缓冲的总大小小于 highWaterMark 设置的阈值时,调用 writable.write() 会返回 true。 一旦内部缓冲的大小达到或超过 highWaterMark 时,则会返回 false。
stream API 的主要目标,特别是 stream.pipe(),是为了限制数据的缓冲到可接受的程度,也就是读写速度不一致的源头与目的地不会压垮内存。
因为 Duplex 和 Transform 都是可读又可写的,所以它们各自维护着两个相互独立的内部缓冲器用于读取和写入, 这使得它们在维护数据流时,读取和写入两边可以各自独立地运作。 例如,net.Socket 实例是 Duplex 流,它的可读端可以消费从 socket 接收的数据,而可写端则可以将数据写入到 socket。 因为数据写入到 socket 的速度可能比接收数据的速度快或者慢,所以在读写两端独立地进行操作(或缓冲)就显得很重要了。
可读流:两种读取模式
可读流运作于两种模式之一:流动模式(flowing)或暂停模式(paused)。
在流动模式中,数据自动从底层系统读取,并通过 EventEmitter 接口的事件尽可能快地被提供给应用程序。
在暂停模式中,必须显式调用 stream.read() 读取数据块。
- 所有可读流都开始于暂停模式,可以通过以下方式切换到流动模式:
- 添加 'data' 事件句柄。
- 调用 stream.resume()。
- 调用 stream.pipe()。
- 读流可以通过以下方式切换回暂停模式:
- 如果没有管道目标,则调用 stream.pause()。
- 如果有管道目标,则移除所有管道目标。调用 stream.unpipe() 可以移除多个管道目标。
只有提供了消费或忽略数据的机制后,可读流才会产生数据。 如果消费的机制被禁用或移除,则可读流会停止产生数据。
为了向后兼容,移除 'data' 事件句柄不会自动地暂停流。 如果有管道目标,一旦目标变为 drain 状态并请求接收数据时,则调用 stream.pause() 也不能保证流会保持暂停模式。
如果可读流切换到流动模式,且没有可用的消费者来处理数据,则数据将会丢失。 例如,当调用 readable.resume() 时,没有监听 'data' 事件或 'data' 事件句柄已移除。
添加 'readable' 事件句柄会使流自动停止流动,并通过 readable.read() 消费数据。 如果 'readable' 事件句柄被移除,且存在 'data' 事件句柄,则流会再次开始流动。
可读流:三种状态
可读流的两种模式是对发生在可读流中更加复杂的内部状态管理的一种简化的抽象。
在任意时刻,可读流会处于以下三种状态之一:
- readable.readableFlowing === null
- readable.readableFlowing === false
- readable.readableFlowing === true
当 readable.readableFlowing 为 null 时,没有提供消费流数据的机制,所以流不会产生数据。 在这个状态下,监听 'data' 事件、调用 readable.pipe()、或调用 readable.resume() 都会使 readable.readableFlowing 切换到 true,可读流开始主动地产生数据并触发事件。
调用 readable.pause()、readable.unpipe()、或接收到背压,则 readable.readableFlowing 会被设为 false,暂时停止事件流动但不会停止数据的生成。 在这个状态下,为 'data' 事件绑定监听器不会使 readable.readableFlowing 切换到 true。
当 readable.readableFlowing 为 false 时,数据可能会堆积在流的内部缓冲中。
可读流:选择一种接口风格
可读流的 API 贯穿了多个 Node.js 版本,且提供了多种方法来消费流数据。 开发者通常应该选择其中一种方法来消费数据,不要在单个流使用多种方法来消费数据。 混合使用 on('data')、on('readable')、pipe() 或异步迭代器,会导致不明确的行为。
对于大多数用户,建议使用 readable.pipe(),因为它是消费流数据最简单的方式。 如果开发者需要精细地控制数据的传递与产生,可以使用 EventEmitter、readable.on('readable')/readable.read() 或 readable.pause()/readable.resume()。
Events: 'finish' and 'end'
'finish'事件来自stream.Writable;'end'事件来自stream.Readable类。
在调用了stream.end()并且stream._transform()处理了全部数据块之后, 'finish'事件触发。
transform._flush()中的回调函数被调用之后,所有数据已经输出,此时,'end'事件触发