深入探究node之Transform
发布时间 - 2026-01-11 02:25:41 点击率:次本文详细的介绍了node Transform ,分享给大家,希望此文章对各位有所帮助。

Transform流特性
在开发中直接接触Transform流的情况不是很多,往往是使用相对成熟的模块或者封装的API来完成流的处理,最为特殊的莫过于through2模块和gulp流操作。那么,Transform流到底有什么特点呢?
从名称上说,Transform意为处理,类似于生产流水线上的每一道工序,每道工序针对到来的产品作相应的处理;从结构上看,Transform是一个双工流,通俗的解释它既可以作为可读流,也可作为可写流。但是,node却对Transform流针对其特性做了更为特殊的定制,使Transform不是单纯的Duplex流。
Transform流由于包含了Readable和Writeable特性,因此Transform在实际使用中有着多种方式:它既可以只作为消费者消费数据,也可同时作为生产者和消费者完成数据中间处理。下面将逐渐深入内部阐述Transform的运行机理及使用技巧。
Transform内部架构
上图表示一个Transform实例的组成部分:Readable部分缓冲(数组)、内部_read函数、Writeable部分缓冲(链表)、内部_write函数、Transform实例必须实现的内部_transform函数以及系统提供的回调函数afterTransform。由于Transform实例同时拥有两部分缓冲,因此2个缓冲的存储、消耗的顺序也就需要了解,这对于后面使用原生Transform编写代码有很大的指导意义。
传统意义的流(即Readable和Writeable)的实现者都需要实现对应的内部函数_read()和_write(),对于Readable实例而言,_read函数用于准备从源文件中获取数据并添加到读缓冲中;对于Writeable实例_write函数则从写缓冲链表中一次刷入到磁盘中。它们分别对应了读写流程的首尾步骤,具体可以关注node中的Stream一文。
而Transform中的_read和_write函数的实现大有不同,由于需要兼顾流的处理,因此着重分析Transform的内部函数执行流程。
示例demo:
readable.pipe(transform);
以上段示例代码为例,transform作为消费者消费readable。
Transform的实例transform拥有transormState和readableState属性,保存了相关属性,如tranform状态信息、回调函数存储和编码等。transform作为消费者,会在其write函数中消费数据,在node中的Stream文中介绍了write函数的实现细节,通过内部调用_write函数实现数据的写入。而在Transform中_write函数已经重写:
1.保存transform收到的chunk数据、编码和函数(执行刷新写缓冲)
2.在一定条件下执行_read函数(当状态为非转换下,只要读缓冲大小未超过设定的大小,则执行_read)
如果一切顺利,readable的数据会顺利执行transform的**write->_write->_read**,那么原本负责填充读缓冲的_read在Transform中发生了哪些改变呢?
Transform.prototype._read = function(n) {
var ts = this._transformState;
if (ts.writechunk !== null && ts.writecb && !ts.transforming) {
ts.transforming = true;
this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform);
} else {
// mark that we need a transform, so that any data that comes in
// will get processed, now that we've asked for it.
ts.needTransform = true;
}
};
可见,_read的实现非常简单,根据条件选择执行_transform函数。需要注意的是_read的参数n并未有使用,因为是否插入数据至读缓冲是由开发者在_transform中来决定。相信大家对_transform函数并不陌生,node规定Transform实例必须提供_transform函数,而该函数正是在_read中调用。
_transform有三个参数,第一个为待处理的chunk数据,第二个为编码,第三个为回调函数。前两个参数很好理解,我们可以在_transform中尽情的处理数据,最后调用回调函数完成处理。那么,这个回调函数究竟是什么? 它就是Transform架构图中的afterTransform函数,它有几个功能:
1.清空各种状态信息,如transformState对象的一些属性,用于下次处理数据使用
2.可选的保存处理结果至读缓冲区
3.刷新写缓冲区,执行下一阶段的数据流处理
可见,在afterTransform函数执行后,才基本宣告transform第一阶段的结束。为何是第一阶段呢?因为transform才完成了作为消费者(即Writeable)的作用,如果用户在_transform中传入了数据到写缓冲区,那么此时transform也同时是一个生产者,提供数据让后面的消费者消费数据,这就涉及到了Transform使用上的问题。
Transform的生产消费实例
const stream = require('stream')
var c = 0;
const readable = stream.Readable({
highWaterMark: 2,
read: function () {
var data = c < 26 ? String.fromCharCode(c++ + 97) : null;
console.log('push', data);
this.push(data);
}
})
const transform = stream.Transform({
highWaterMark: 2,
transform: function (buf, enc, next) {
console.log('transform', buf.toString());
next(null, buf);
}
})
readable.pipe(transform);
示例代码很简单,创建了一个可读流,向消费者提供a-z的小写字母;创建了一个转换流,在_transform函数中针对数据并不做处理仅作打点输出,并向回调函数传递数据至读缓冲区。我们的目的是通过transform输出26个小写字母,但是当前程序执行的结果并不让人满意:
执行结果:
push a
push b
transform a
push c
transform b
push d
push e
push f
tranform仅仅处理到字母b,readable也仅仅提供了a-f的数据便戛然而止,这是为何?
这一切都归结于transform对象。认真读过上文后我们知道,所有的Transform实例同时有两个缓冲区,其中写缓冲区用来接收生产者的数据进行转换操作,读缓冲区则缓存数据给消费者使用。而在当前的实现中,transform._transform函数输出了待处理数据,同时执行next(null, buf);。该函数上文已有分析,即afterTransform函数,第一个参数为Error实例,第二个则为存入读缓冲区的数据。在本例中,执行完_transform后将处理后的数据存入读缓冲区,等待后面的消费者消费读缓冲区的数据。可是,transform后面没有消费者了,因此transform在处理完字母b存入读缓冲区后,读缓冲区已经满了(设定highWaterMark为2,即读写缓冲区的最大值均为2字节)。当字母c、d也执行到tranform._write后,由于不满足执行transform._read的条件无法执行transform._transform函数,更无法执行afterTransform函数,导致无法刷新写缓冲区的数据,造成字母c、d贮存在写缓冲区。而字母e、f则由于transform的写缓冲区满(transform.write()返回false),只有存储在readable的读缓冲区中,等待消费。这就造成了死循环,readable和transform所有的缓冲区都满了,流也就停止了。
解决这个问题的方法很简单,有两种不同方案:
1.transform的读缓冲区保持为空
2.增加消费者消费transform的读缓冲区
其实本质上都是让transform的读缓冲区得到消耗。
第一种方案:
保证transform的读缓冲区为空:
const transform = stream.Transform({
highWaterMark: 2,
transform: function (buf, enc, next) {
console.log('transform', buf.toString())
next(null, null)
}
})
只需向next函数传入null即可,这样transform消费完数据后即宣告数据处理结束,读缓冲区始终为空。
第二种方案:
添加消费者:
const transform = stream.Transform({
highWaterMark: 2,
transform: function (buf, enc, next) {
console.log('transform', buf.toString())
next(null, buf)
}
})
readable.pipe(transform).pipe(process.stdout);
transform实现不变,只是添加了消费者process.stdout。这样也同时保证了transform的读缓冲区处于可添加状态,也给了afterTransform函数刷新写缓冲区的机会,开启新的数据处理流程。
through2的实现
through2的重头戏在于Transform流,使用through2的API可方便的创建一个Transform实例,完成数据流的处理。
function through2 (construct) {
return function (options, transform, flush) {
if (typeof options == 'function') {
flush = transform
transform = options
options = {}
}
if (typeof transform != 'function')
transform = noop
if (typeof flush != 'function')
flush = null
return construct(options, transform, flush)
}
}
module.exports = through2(function (options, transform, flush) {
var t2 = new DestroyableTransform(options)
t2._transform = transform
if (flush)
t2._flush = flush
return t2
})
可见,through2模块仅仅是封装了Transform的构造函数,并封装了更为易用的objectMode模式。之所以建议使用through2创建Transform对象,不仅仅是因为其提供了方便的API,更主要的是为了兼容性。Transform对象是属于Stream2.0的特性,早先版本的node并没有实现,而通过through2创建的Transform实例在之前版本的node下仍可正常使用,这是由于through2并未引用node默认提供的stream模块,而是使用社区中较为流行的“readable-stream”模块。
总结
本文旨在深入through2中的使用的Transform流进行探究,并作为上一篇文章node中的stream的回顾和应用。通过文末简单的示例了解Transform在开发中可能出现的问题,学会随意切换Transform的生产者和消费者的身份,更好的指导实际开发。
以上所述是小编给大家介绍的node之Transform ,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!
# node
# Transform
# 一行命令搞定node.js 版本升级
# windows系统下简单nodejs安装及环境配置
# 跟我学Nodejs(一)--- Node.js简介及安装开发环境
# Node.js(安装
# 启动
# 测试)
# nodejs中exports与module.exports的区别详细介绍
# Node.js实战 建立简单的Web服务器
# nodejs npm install全局安装和本地安装的区别
# NodeJS学习笔记之FS文件模块
# NodeJS学习笔记之Http模块
# nodejs开发环境配置与使用
# 回调
# 的是
# 是一个
# 这是
# 为空
# 第一个
# 也就
# 而在
# 也可
# 这就
# 第二个
# 给大家
# 很简单
# 数据处理
# 满了
# 双工
# 装了
# 小编
# 流进
# 它既
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Win11怎么设置虚拟桌面 Win11新建多桌面切换操作【技巧】
高端建站如何打造兼具美学与转化的品牌官网?
Laravel如何实现用户角色和权限系统_Laravel角色权限管理机制
香港服务器网站推广:SEO优化与外贸独立站搭建策略
在线制作视频网站免费,都有哪些好的动漫网站?
javascript基本数据类型及类型检测常用方法小结
html如何与html链接_实现多个HTML页面互相链接【互相】
Win11怎样安装网易有道词典_Win11安装词典教程【步骤】
如何在HTML表单中获取用户输入并用JavaScript动态控制复利计算循环
Linux系统命令中tree命令详解
,网页ppt怎么弄成自己的ppt?
JavaScript如何实现路由_前端路由原理是什么
如何在IIS中新建站点并配置端口与物理路径?
如何快速登录WAP自助建站平台?
如何基于PHP生成高效IDC网络公司建站源码?
如何在IIS7上新建站点并设置安全权限?
java中使用zxing批量生成二维码立牌
Win11摄像头无法使用怎么办_Win11相机隐私权限开启教程【详解】
Laravel怎么使用Blade模板引擎_Laravel模板继承与Component组件复用【手册】
Windows10电脑怎么查看硬盘通电时间_Win10使用工具检测磁盘健康
Laravel如何使用Service Provider注册服务_Laravel服务提供者配置与加载
Laravel如何使用.env文件管理环境变量?(最佳实践)
简单实现Android验证码
如何正确选择百度移动适配建站域名?
利用 Google AI 进行 YouTube 视频 SEO 描述优化
Laravel怎么使用Markdown渲染文档_Laravel将Markdown内容转HTML页面展示【实战】
Laravel如何保护应用免受CSRF攻击?(原理和示例)
Mybatis 中的insertOrUpdate操作
如何快速搭建FTP站点实现文件共享?
广州网站制作公司哪家好一点,广州欧莱雅百库网络科技有限公司官网?
Laravel如何记录自定义日志?(Log频道配置)
Laravel如何配置中间件Middleware_Laravel自定义中间件拦截请求与权限校验【步骤】
如何在景安云服务器上绑定域名并配置虚拟主机?
电商网站制作多少钱一个,电子商务公司的网站制作费用计入什么科目?
专业型网站制作公司有哪些,我设计专业的,谁给推荐几个设计师兼职类的网站?
Laravel模型事件有哪些_Laravel Model Event生命周期详解
百度浏览器网页无法复制文字怎么办 百度浏览器复制修复
Laravel Eloquent模型如何创建_Laravel ORM基础之Model创建与使用教程
如何在万网主机上快速搭建网站?
Laravel如何使用Vite进行前端资源打包?(配置示例)
中山网站推广排名,中山信息港登录入口?
如何在云虚拟主机上快速搭建个人网站?
Laravel怎么防止CSRF攻击_Laravel CSRF保护中间件原理与实践
Laravel PHP版本要求一览_Laravel各版本环境要求对照
Laravel如何获取当前登录用户信息_Laravel Auth门面使用与Session用户读取【技巧】
如何快速搭建个人网站并优化SEO?
昵图网官网入口 昵图网素材平台官方入口
网站设计制作书签怎么做,怎样将网页添加到书签/主页书签/桌面?
Java Adapter 适配器模式(类适配器,对象适配器)优缺点对比
Swift中swift中的switch 语句

