Node.js从字符串生成文件流的实现方法


Posted in Javascript onAugust 18, 2019

一.背景

在文件相关的数据加工等场景下,经常面临生成的物理文件应该如何处理的问题,比如:

生成的文件放到哪里,路径存在不存在?

临时文件何时清理,如何解决命名冲突,防止覆盖?

并发场景下的读写顺序如何保证?

……

对于读写物理文件带来的这些问题,最好的解决办法就是 不写文件 。然而,一些场景下想要不写文件可不那么容易,比如文件上传

二.问题

文件上传一般通过表单提交来实现,例如:

var FormData = require('form-data');
var fs = require('fs');

var form = new FormData();
form.append('my_file', fs.createReadStream('/foo/bar.jpg'));
form.submit('example.org/upload', function(err, res) {
 console.log(res.statusCode);
});

(摘自 Form-Data )

不想写物理文件的话,可以这样做:

const FormData = require('form-data');

const filename = 'my-file.txt';
const content = 'balalalalala...变身';

const formData = new FormData();
// 1.先将字符串转换成Buffer
const fileContent = Buffer.from(content);
// 2.补上文件meta信息
formData.append('file', fileContent, {
 filename,
 contentType: 'text/plain',
 knownLength: fileContent.byteLength
});

也就是说,文件流除了能够提供数据外,还具有一些 meta 信息,如文件名、文件路径等 ,而这些信息是普通 Stream 所不具备的。那么,有没有办法凭空创建一个“真正的”文件流?

三.思路

要想创建出“真正的”文件流,至少有正反 2 种思路:

给普通流添上文件相关的 meta 信息

先拿到一个真正的文件流,再改掉其数据和 meta 信息

显然,前者更灵活一些,并且实现上能够做到完全不依赖文件

文件流的生产过程

沿着凭空创造的思路,探究 fs.createReadStream API 的 内部实现 之后发现,生产文件流的关键过程如下:

function ReadStream(path, options) {
 // 1.打开path指定的文件
 if (typeof this.fd !== 'number')
  this.open();
}

ReadStream.prototype.open = function() {
 fs.open(this.path, this.flags, this.mode, (er, fd) => {
  // 2.拿到文件描述符并持有
  this.fd = fd;
  this.emit('open', fd);
  this.emit('ready');
  // 3.开始流式读取数据
  // read来自父类Readable,主要调用内部方法_read
  // ref: https://github.com/nodejs/node/blob/v10.16.3/lib/_stream_readable.js#L390
  this.read();
 });
};

ReadStream.prototype._read = function(n) {
 // 4.从文件中读取一个chunk
 fs.read(this.fd, pool, pool.used, toRead, this.pos, (er, bytesRead) => {
  let b = null;
  if (bytesRead > 0) {
   this.bytesRead += bytesRead;
   b = thisPool.slice(start, start + bytesRead);
  }
  // 5.(通过触发data事件)吐出一个chunk,如果还有数据,process.nextTick再次this.read,直至this.push(null)触发'end'事件
  // ref: https://github.com/nodejs/node/blob/v10.16.3/lib/_stream_readable.js#L207
  this.push(b);
 });
};

P.S.其中第 5 步相对复杂, this.push(buffer) 既能触发下一个 chunk 的读取( this.read() ),也能在数据读完之后(通过 this.push(null) )触发 'end' 事件,具体见 node/lib/_stream_readable.js

重新实现文件流

既然已经摸清了文件流的生产过程,下一步自然是 替换掉所有文件操作,直至文件流的实现完全不依赖文件 ,例如:

// 从文件中读取一个chunk
fs.read(this.fd, pool, pool.used, toRead, this.pos, (er, bytesRead) => {
 /* ... */
});

// 换成
this._fakeReadFile(this.fd, pool, pool.used, toRead, this.pos, (bytesRead) => {
 /* ... */
});

// 从输入字符串对应的Buffer中copy出一个chunk
ReadStream.prototype._fakeReadFile = function(_, buffer, offset, length, position, cb) {
 position = position || this.input._position;
 // fake read file async
 setTimeout(() => {
  let bytesRead = 0;
  if (position < this.input.byteLength) {
   bytesRead = this.input.copy(buffer, offset, position, position + length - 1);
   this.input._position += bytesRead;
  }
  cb(bytesRead);
 }, 0);
}

即从中剔除文件操作,用基于字符串的操作去替代它们

四.解决方案

如此这般,就有了 ayqy/string-to-file-stream ,用来凭空创建文件流:

string2fileStream('string-content') === fs.createReadStream(/* path to a text file with content 'string-content' */)`

例如:

const string2fileStream = require('string-to-file-stream');

const input = 'Oh, my great data!';
const s = string2fileStream(input);
s.on('data', (chunk) => {
 assert.equal(chunk.toString(), input);
});
生成的流同样能够具有文件 meta 信息:

const string2fileStream = require('string-to-file-stream');

const formData = new FormData();
formData.append('file', string2fileStream('my-string-data', { path: './abc.txt' }));
form.submit('example.org/upload', function(err, res) {
 console.log(res.statusCode);
});

足够以假乱真

参考资料

fs.createReadStream(path[, options])

fs/streams.js

_stream_readable.js

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery 1.0.2
Oct 11 Javascript
多广告投放代码 推荐
Nov 13 Javascript
js自定义事件代码说明
Jan 31 Javascript
纯js实现瀑布流展现照片(自动适应窗口大小)
Apr 08 Javascript
深入理解JavaScript系列(40):设计模式之组合模式详解
Mar 04 Javascript
JavaScript中rem布局在react中的应用
Dec 09 Javascript
js实现无缝循环滚动
Jun 23 Javascript
JS实现线性表的链式表示方法示例【经典数据结构】
Apr 11 Javascript
移动端使用localResizeIMG4压缩图片
Apr 22 Javascript
Vuex mutitons和actions初使用详解
Mar 04 Javascript
Layui 数据表格批量删除和多条件搜索的实例
Sep 04 Javascript
vant IndexBar实现的城市列表的示例代码
Nov 20 Javascript
微信公众号生成新浪短网址的实现(快速生成)
Aug 18 #Javascript
js 实现 list转换成tree的方法示例(数组到树)
Aug 18 #Javascript
详解ES6 Promise的生命周期和创建
Aug 18 #Javascript
vue-cli3配置与跨域处理方法
Aug 17 #Javascript
vue中获取滚动table的可视页面宽度调整表头与列对齐(每列宽度不都相同)
Aug 17 #Javascript
vue 使用element-ui中的Notification自定义按钮并实现关闭功能及如何处理多个通知
Aug 17 #Javascript
微信小程序开发之map地图组件定位并手动修改位置偏差
Aug 17 #Javascript
You might like
PHP常用日期加减计算方法实例小结
2018/07/31 PHP
爱恋千雪-US-AscII加密解密工具(网页加密)下载
2007/06/06 Javascript
JScript 脚本实现文件下载 一般用于下载木马
2009/10/29 Javascript
实现点击列表弹出列表索引的两种方式
2013/03/08 Javascript
在线一元二次方程计算器实例(方程计算器在线计算)
2013/12/22 Javascript
jquery删除提示框弹出是否删除对话框
2014/01/07 Javascript
jQuery实现数字加减效果汇总
2014/12/16 Javascript
jQuery+css实现的tab切换标签(兼容各浏览器)
2016/01/28 Javascript
JavaScript中使用数组方法汇总
2016/02/16 Javascript
全面解析Bootstrap中tab(选项卡)的使用方法
2016/06/06 Javascript
【经典源码收藏】jQuery实用代码片段(筛选,搜索,样式,清除默认值,多选等)
2016/06/07 Javascript
详解jQuery停止动画——stop()方法的使用
2016/12/14 Javascript
利用CSS、JavaScript及Ajax实现图片预加载的三大方法
2017/01/22 Javascript
node.js利用mongoose获取mongodb数据的格式化问题详解
2017/10/06 Javascript
使用layer弹窗和layui表单实现新增功能
2018/08/09 Javascript
Vue实现购物车的全选、单选、显示商品价格代码实例
2019/05/06 Javascript
JS自定义对象创建与简单使用方法示例
2020/01/15 Javascript
JS绘图Flot应用图形绘制异常解决方案
2020/10/16 Javascript
ant design vue导航菜单与路由配置操作
2020/10/28 Javascript
[02:39]DOTA2国际邀请赛助威团西雅图第一天
2013/08/08 DOTA
[49:27]2018DOTA2亚洲邀请赛 4.4 淘汰赛 TNC vs VG 第一场
2018/04/05 DOTA
Python实现判断一个字符串是否包含子串的方法总结
2017/11/21 Python
python3反转字符串的3种方法(小结)
2019/11/07 Python
Python3监控windows,linux系统的CPU、硬盘、内存使用率和各个端口的开启情况详细代码实例
2020/03/18 Python
Tensorflow卷积实现原理+手写python代码实现卷积教程
2020/05/22 Python
Python如何输出警告信息
2020/07/30 Python
2014年圣诞节倒计时网页的制作过程
2014/12/05 HTML / CSS
VC++笔试题
2014/10/13 面试题
饮料业务员岗位职责
2013/12/15 职场文书
财务总监管理职责范文
2014/03/09 职场文书
国旗下讲话演讲稿
2014/05/08 职场文书
2014年酒店工作总结与计划
2014/11/17 职场文书
员工工作失职检讨书范文!
2019/07/03 职场文书
thinkphp 获取控制器及控制器方法
2021/04/16 PHP
go 原生http web 服务跨域restful api的写法介绍
2021/04/27 Golang
my.ini优化mysql数据库性能的十个参数(推荐)
2021/05/26 MySQL