我们在 HTTP 中看过两个可写流的例子,即服务器可以向response对象中写入数据,而request返回的请求对象也可以写入数据。

可写流是 Node 中广泛使用的概念。这种对象拥有write方法,你可以传递字符串或Buffer对象,来向流写入一些数据。它们end方法用于关闭流,并且还可以接受一个可选值,在流关闭之前将其写入流。 这两个方法也可以接受回调作为附加参数,当写入或关闭完成时它们将被调用。

我们也可以使用fs模块的createWriteStream,建立一个指向本地文件的输出流。你可以调用该方法返回的结果对象的write方法,每次向文件中写入一段数据,而不是像writeFile那样一次性写入所有数据。

可读流则略为复杂。传递给 HTTP 服务器回调的request绑定,以及传递给 HTTP 客户端回调的response对象都是可读流(服务器读取请求并写入响应,而客户端则先写入请求,然后读取响应)。读取流需要使用事件处理器,而不是方法。

Node 中发出的事件都有一个on方法,类似浏览器中的addEventListener方法。该方法接受一个事件名和一个函数,并将函数注册到事件上,接下来每当指定事件发生时,都会调用注册的函数。

可读流有data事件和end事件。data事件在每次数据到来时触发,end事件在流结束时触发。该模型适用于“流”数据,这类数据可以立即处理,即使整个文档的数据没有到位。我们可以使用createReadStream函数创建一个可读流,来读取本地文件。

这段代码创建了一个服务器并读取请求正文,然后将读取到的数据全部转换成大写,并使用流写回客户端。

  1. const {createServer} = require("http");
  2. createServer((request, response) => {
  3. response.writeHead(200, {"Content-Type": "text/plain"});
  4. request.on("data", chunk =>
  5. response.write(chunk.toString().toUpperCase()));
  6. request.on("end", () => response.end());
  7. });
  8. }).listen(8000);

传递给data处理器的chunk值是一个二进制Buffer对象,我们可以使用它的toString方法,通过将其解码为 UTF-8 编码的字符,来将其转换为字符串。

下面的一段代码,和上面的服务(将字母转换成大写)一起运行时,它会向服务器发送一个请求并输出获取到的响应数据:

  1. const {request} = require("http");
  2. request({
  3. hostname: "localhost",
  4. port: 8000,
  5. method: "POST"
  6. }, response => {
  7. response.on("data", chunk =>
  8. process.stdout.write(chunk.toString()));
  9. }).end("Hello server");
  10. // → HELLO SERVER

该示例代码向process.stdout(进程的标准输出流,是一个可写流)中写入数据,而不使用console.log,因为console.log函数会在输出的每段文本后加上额外的换行符,在这里不太合适。