使用Node.js流工作

169 阅读5分钟

简介

流是大多数Node.js应用程序依赖的主要功能之一,特别是在处理HTTP请求、读/写文件和进行套接字通信时。流是非常可预测的,因为在使用流时,我们总是可以期待数据、错误和结束事件。

本文将教导Node开发者如何使用流来有效地处理大量的数据。这是Node开发者面临的一个典型的现实世界的挑战,当他们必须处理一个大的数据源时,而一次性处理这些数据可能是不可行的。

这篇文章将涵盖以下主题。

流的类型

以下是Node.js中流的四种主要类型。

  • 可读流。可读流负责从源文件中读取数据
  • 可写流。可写流负责将特定格式的数据写入文件中
  • 双面流。双面流是同时实现可读流和可写流接口的流
  • 转换流。转换流是一种双工流,它读取数据,转换数据,然后以指定的格式写入转换后的数据

何时使用Node.js流

当我们在处理那些大到无法读入内存并作为一个整体处理的文件时,流就会派上用场。

例如,如果你正在开发一个视频会议/流媒体应用程序,需要以较小的块来传输数据,以实现大批量的网络流媒体,同时避免网络延迟,那么考虑Node.js流是一个不错的选择。

批处理过程

批处理是一种常见的数据优化模式,它涉及到分块收集数据,将这些数据存储在内存中,一旦所有数据都存储在内存中,就将它们写入磁盘。

让我们来看看一个典型的批处理过程。

const fs = require("fs");
const https = require("https");
const url = "some file url";
https.get(url, (res) => {
  const chunks = [];
  res
    .on("data", (data) => chunks.push(data))
    .on("end", () =>
      fs.writeFile("file.txt", Buffer.concat(chunks), (err) => {
        err ? console.error(err) : console.log("saved successfully!");
      })
    );
});

在这里,所有的数据被推入一个数组。当数据事件被触发,一旦 "结束 "事件被触发,表明我们已经完成了数据的接收,我们继续使用fs.writeFileBuffer.concat 方法将数据写入文件。

批处理的主要缺点是内存分配不足,因为所有的数据在写到磁盘之前都存储在内存中。

在我们收到数据的时候写数据是处理大文件的一个更有效的方法。这就是流派上用场的地方。

在Node.js中组成流

Node.jsfs 模块暴露了一些原生的Node Stream API,它可以被用来组成流。

我们将涵盖可读、可写和转换流。如果你想了解更多,可以阅读我们关于Node.js中双工流的博文。

组建可写流

const fs = require("fs");
const fileStream = fs.createWriteStream('./file.txt')
for (let i = 0; i <= 20000; i++) {
  fileStream.write("Hello world welcome to Node.js\n"
  );
}

可写流是使用createWriteStream() 方法创建的,该方法需要把要写入的文件路径作为参数。
运行上述代码段将在你的当前目录下创建一个名为file.txt 的文件,其中有20,000行Hello world welcome to Node.js

组成可读流

const fs = require("fs");
const fileStream = fs.createReadStream("./file.txt");
fileStream
  .on("data", (data) => {
    console.log("Read data:", data.toString());
  })
  .on("end", () => { console.log("No more data."); });

./file.txt 在这里,data 事件处理程序将在每次读取数据块时执行,而end 事件处理程序将在没有数据时执行。
运行上述代码段将把Hello world welcome to Node.js 字符串的20,000行记录到控制台。

组成转换流

转换流具有可读和可写的特性。它允许对输入数据进行处理,然后以处理后的格式输出数据。

为了创建一个转换流,我们需要从Node.js流模块中导入Transform 类。transform 流构造器接受一个包含数据处理/转换逻辑的函数。

const fs = require("fs");
const { Transform } = require("stream");
const fileStream= fs.createReadStream("./file.txt");
const transformedData= fs.createWriteStream("./transformedData.txt");

const uppercase = new Transform({
  transform(chunk, encoding, callback) {
    callback(null, chunk.toString().toUpperCase());
  },
});

fileStream.pipe(uppercase).pipe(transformedData);

在这里,我们创建了一个新的transform 流,其中包含一个函数,期望有三个参数:第一个是数据的chunk ,第二个是encoding (如果数据块是一个字符串,这就很方便了),然后是一个callback ,这个函数会被调用,并带有转换后的结果。

运行上面的片段将把./file.txt 中的所有文本转换为大写字母,然后将其写入transformedData.txt
如果我们运行这个脚本并打开结果文件,我们会看到所有的文本都被转换为大写字母。

流水线

管道流是一种重要的技术,用于将多个流连接在一起。当我们需要将复杂的处理分解成较小的任务并按顺序执行时,它就会派上用场。Node.js为此提供了一个本地pipe 方法。

fileStream.pipe(uppercase).pipe(transformedData);

有关上述代码段的更多细节,请参考构成转换流下的代码段。

错误处理Node.js流

使用管道的错误处理

Node 10引入了管道API,以加强对Node.js流的错误处理。pipeline 方法接受任何数量的streams ,后面有一个callback 函数,处理我们pipeline 中的任何错误,一旦pipeline 完成,就会执行。

pipeline(...streams, callback)


const fs = require("fs");
const { pipeline, Transform } = require("stream");

pipeline(
  streamA,
  streamB,
  streamC,
  (err) => {
    if (err) {
      console.error("An error occured in pipeline.", err);
    } else {
      console.log("Pipeline execcution successful");
    }
  }
);

当使用pipeline ,一系列的流应该按照需要执行的顺序依次传递。

使用管道的错误处理

我们还可以使用管道来处理流错误,如下所示。

const fs = require("fs");
const fileStream= fs.createReadStream("./file.txt");
let b = otherStreamType()
let c = createWriteStream()
fileStream.on('error', function(e){handleError(e)})
.pipe(b)
.on('error', function(e){handleError(e)})
.pipe(c)
.on('error', function(e){handleError(e)});

从上面的片段中可以看出,我们必须为每个创建的pipe ,创建一个error 事件处理器。有了这个,我们就可以跟踪错误的上下文,这在调试时就很有用。这种技术的缺点是它的冗长性。

总结

在这篇文章中,我们已经探讨了Node.js流,何时使用它们,以及如何实现它们。

对Node.js流的了解是必不可少的,因为它们是处理大型数据集时需要依赖的一个伟大工具。请查看Node.js API文档,了解更多关于流的信息。

The postWorking with Node.js streamsappeared first onLogRocket Blog.