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 相关文章推荐
javascript jQuery $.post $.ajax用法
Jul 09 Javascript
用JQuery 判断某个属性是否存在hasAttr的解决方法
Apr 26 Javascript
jquery datepicker参数介绍和示例
Apr 15 Javascript
JavaScript获取表单enctype属性的方法
Apr 02 Javascript
关注jquery技巧提高jquery技能(前端开发必学)
Nov 02 Javascript
JS实现的倒计时效果实例(2则实例)
Dec 23 Javascript
jquery删除table当前行的实例代码
Oct 07 Javascript
浅析如何利用JavaScript进行语音识别
Oct 27 Javascript
js闭包用法实例详解
Dec 13 Javascript
浅谈Vue.use的使用
Aug 29 Javascript
jQuery+css last-child实现选择最后一个子元素操作示例
Dec 10 jQuery
Vue绑定用户接口实现代码示例
Nov 04 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 生成自动创建文件夹并上传文件的示例代码
2014/03/07 PHP
thinkPHP5实现的查询数据库并返回json数据实例
2017/10/23 PHP
html+js实现动态显示本地时间
2013/09/21 Javascript
JavaScript程序员应该知道的45个实用技巧
2014/03/04 Javascript
JavaScript forEach()遍历函数使用及介绍
2015/07/08 Javascript
基于jQuery的checkbox全选问题分析
2016/11/18 Javascript
JS实现根据密码长度显示安全条功能
2017/03/08 Javascript
微信小程序本地缓存数据增删改查实例详解
2017/05/24 Javascript
详解JS取出两个数组中的不同或相同元素
2019/03/20 Javascript
小程序组件之自定义顶部导航实例
2019/06/12 Javascript
layer实现登录弹框,登录成功后关闭弹框并调用父窗口的例子
2019/09/11 Javascript
在vue中使用echars实现上浮与下钻效果
2019/11/08 Javascript
vue 封装 Adminlte3组件的实现
2020/03/18 Javascript
vue Element左侧无限级菜单实现
2020/06/10 Javascript
vue中实现点击变成全屏的多种方法
2020/09/27 Javascript
通过实例解析javascript Date对象属性及方法
2020/11/04 Javascript
举例讲解Python中metaclass元类的创建与使用
2016/06/30 Python
Python 绘图和可视化详细介绍
2017/02/11 Python
python如何使用正则表达式的前向、后向搜索及前向搜索否定模式详解
2017/11/08 Python
对Tensorflow中的矩阵运算函数详解
2018/07/27 Python
Python判断字符串是否为字母或者数字(浮点数)的多种方法
2018/08/03 Python
Django REST framework 分页的实现代码
2019/06/19 Python
python tkinter控件布局项目实例
2019/11/04 Python
python des,aes,rsa加解密的实现
2021/01/16 Python
HTML5实现分享到微信好友朋友圈QQ好友QQ空间微博二维码功能
2018/01/03 HTML / CSS
美国办公用品购物网站:Quill.com
2016/09/01 全球购物
美国克罗格超市在线购物:Kroger
2019/06/21 全球购物
韩国商务邀请函
2014/01/14 职场文书
高二物理教学反思
2014/02/08 职场文书
体育教师自我鉴定
2014/02/12 职场文书
2014县委书记四风对照检查材料思想汇报
2014/09/21 职场文书
css常用字体属性与背景属性介绍
2022/02/28 HTML / CSS
Python学习之os包使用教程详解
2022/03/21 Python
部分武汉产收音机展览
2022/04/07 无线电
CSS文本阴影 text-shadow 悬停效果详解
2022/05/25 HTML / CSS
nginx七层负载均衡配置详解
2022/07/15 Servers