Node.js Stream 的两种模式

更新日期: 2021-08-03阅读: 1.6k标签: node

这是来自「  Nodejs技术   」交流群一位读者朋友提的一个问题,“ 如果注释掉 req.on('data') 事件监听,end 事件就收不到了,进而永远也不会执行 res.end(),请求会被一直挂着,为什么? ”。

如果你读到这里,也可以先思考下这个问题!

const http = require('http');
http.createServer((req, res) => {
let data = '';
// req.on('data', chunk => {
// data += chunk.toString();
// });
req.on('end', () => {
res.end(data);
});
}).listen(3000);

Node.js 的可读流对象提供了两种模式:流动模式(flowing)、暂停模式(paused),如果你使用管道 pipe() 或异步迭代可能不会关注到这个问题,在它们的内部实现中已经处理好了,如果你是基于事件的 api 来处理流,可能会有这些疑问。

流动模式(flowing)

流动模式下数据自动从底层系统获取,并通过 EventEmitter 提供的事件接口,尽可能快的提供给应用程序。需要注意的是 所有的可读流一开始都处于暂停模式 ,要切换为流动模式,可通过以下几种方式实现:

一:注册 'data' 事件

为可读流对象注册一个 'data' 事件,传入事件处理函数,会把流切换为流动模式,在数据可用时会立即把数据块传送给注册的事件处理函数。

这也是上面的疑问,为什么注释掉 'data' 事件,请求就会一直被挂起。

req.on('data', chunk => {
data += chunk.toString();
});

二:stream.pipe() 方法

调用 pipe() 方法将数据发送到可写流。

readable.pipe(writeable)

可读流的 pipe() 方法实现中也是注册了 'data' 事件,一边读取数据一边写入数据至可写流。可以参见笔者之前的这篇文章 Node.js Stream 模块 pipe 方法使用与实现原理分析 。

Readable.prototype.pipe = function(dest, options) {
const src = this;
src.on('data', ondata);
function ondata(chunk) {
const ret = dest.write(chunk);
if (ret === false) {
...
src.pause();
}
}
...
};

三:stream.resume() 方法

stream.resume() 将处于暂停模式的可读流,恢复触发 'data' 事件,切换为流动模式。

对一开始的示例做一个改造,先调用 stream.resume() 用来耗尽流中的数据,但此时没有做任何的数据处理,之后会收到 end 事件。

const http = require('http');
http.createServer((req, res) => {
req.resume();
req.on('end', () => {
res.end('Ok!');
});
}).listen(3000);

四:异步迭代

无需注册事件监听函数,使用 for...await of 遍历可读流,写法上也很简单。下例,因为用到**顶级 await 特性,**需要在 ES Modules 规范中使用。

// app.mjs
import { createServer as server } from 'http';
import { on } from 'events';
const ee = on(server().listen(3000), 'request');
for await (const [{ url }, res] of ee) {
res.end('OK!');
}

暂停模式

暂停模式也是流一开始时所处的模式,该模式下会触发 'readable' 事件,表示流中有可读取的数据,我们需要不断调用 read() 方法拉取数据,直到返回 null,表示缓冲区中的数据已被耗尽,在 read() 返回 null 后,会再次触发   'readable' 事件,表示仍有可读取的数据,如果此时停止 read() 方法调用,同样的请求也会被挂起。

stream.read(size) 方法从流缓冲区拉取数据,每次返回指定 size 大小的数据,如果不指定 size 则返回内部所有缓冲的数据。

const http = require('http');
http.createServer((req, res) => {
let data = '';
let chunk;
req.on('readable', () => {
while (null !== (chunk = req.read())) {
data += chunk.toString();
}
})
req.on('end', () => {
res.end(data);
});
}).listen(3000);

背压问题思考

以流的形式从可读流拉取数据到可写流,通常**从磁盘读取数据的速度比磁盘写入的速度是快的,如果可写流来不及消费数据造成数据积压(专业术语会称呼这个问题为 “背压”)会怎么样?**也是来自「Nodejs技术栈」交流群读者朋友的疑问,可以思考下,答案可以写在评论区,感兴趣的关注下「Nodejs技术栈」下一次讲解。

总结

流刚开始处于暂停模式,所以注释掉 req.on('data') 事件监听,请求才会一直挂起。在基于流的方式读取文件时,之前通常使用注册 'data' 事件处理函数的方式从可读流中拉取数据,现在 Node.js 支持了异步迭代,更推荐你使用 for...await of 这种方式来读取数据,代码看起来也会更简洁,同步编码思维让人也能更好的理解。

敬请关注「Nodejs技术栈」微信公众号,获取优质文章


链接: https://fly63.com/article/detial/10626

关于 Node.js 里 ES6 Modules 的一次更新说明

关于 Node.js 里 ES6 Modules 的一次更新说明,总结来说:CommonJS 与 ES6 Modules 之间的关键不同在于代码什么时候知道一个模块的结构和使用它。

用node.js开发一个可交互的命令行应用

在这个教程中,我们会开发一个命令行应用,它可以接收一个 CSV 格式的用户信息文件,教程的内容大纲:“Hello,World”,处理命令行参数,运行时的用户输入,异步网络会话,美化控制台的输出,封装成 shell 命令,JavaScript 之外

Node启动https服务器

首先你需要生成https证书,可以去付费的网站购买或者找一些免费的网站,可能会是key或者crt或者pem结尾的。不同格式之间可以通过OpenSSL转换

nodejs 异步转同步

nodej项目在微信环境开发,nodejs的异步特效,会导致请求没有完成就执行下面的代码,出现错误。经过多方查找,可以使用async模块来异步转同步,只有前一个function执行callback,下一个才会执行。

基于node服务器的大文件(G级)上传

3G的大文件分1500个2M二进度文件,通post方法发送给node服务,服务器全部接收到文件后,进组装生成你上文件。

为什么要把 JavaScript 放到服务器端上运行?

JavaScript比C的开发门槛要低,尽管服务器端JavaScript存在已经很多年了,但是后端部分一直没有市场,JavaScript在浏览器中有广泛的事件驱动方面的应用,考虑到高性能、符合事件驱动、没有历史包袱这3个主要原因,JavaScript成为了Node的实现语言。

了解node.js事件循环

node.js的第一个基本论点是I / O的性能消耗是很昂贵。因此,使用当前编程技术的最大浪费来自于等待I / O完成。有几种方法可以处理性能影响

Node.js 应用:Koa2 使用 JWT 进行鉴权

在前后端分离的开发中,通过 Restful API 进行数据交互时,如果没有对 API 进行保护,那么别人就可以很容易地获取并调用这些 API 进行操作。那么服务器端要如何进行鉴权呢?

Node.js 前端开发指南

我们经常跟Node.js打交道,即使你是一名前端开发人员 -- npm脚本,webpack配置,gulp任务,程序打包 或 运行测试等。即使你真的不需要深入理解这些任务,但有时候你会感到困惑,会因为缺少Node.js的一些核心概念而以非常奇怪的方式来编码。

happypack提升项目构建速度

运行在 Node.js 之上的 Webpack 是单线程模型的,也就是说 Webpack 需要处理的任务需要一件件挨着做,不能多个事情一起做。happypack把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!