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下给元素添加事件的方法与代码
Aug 13 Javascript
jquery聚焦文本框与扩展文本框聚焦方法
Oct 12 Javascript
js获取select默认选中的Option并不是当前选中值
May 07 Javascript
jQuery的图片滑块焦点图插件整理推荐
Dec 07 Javascript
深入浅析JavaScript面向对象和原型函数
Feb 06 Javascript
Jquery实现上下移动和排序代码
Oct 17 Javascript
vue2.0 中#$emit,$on的使用详解
Jun 07 Javascript
JavaScript中document.referrer的用法详解
Jul 04 Javascript
BootStrap自定义popover,点击区域隐藏功能的实现
Jan 23 Javascript
Vue slot用法(小结)
Oct 22 Javascript
vue+vuex+json-seiver实现数据展示+分页功能
Apr 11 Javascript
vue 导航锚点_点击平滑滚动,导航栏对应变化详解
Aug 10 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
解决FastCGI 进程超过了配置的活动超时时限的问题
2013/07/03 PHP
Yii框架数据库查询、增加、删除操作示例
2019/10/14 PHP
Laravel 5.2 文档 数据库 ―― 起步介绍
2019/10/21 PHP
jquery 获取 outerHtml 包含当前节点本身的代码
2014/10/30 Javascript
JavaScript Sort 的一个错误用法示例
2015/03/20 Javascript
JavaScript对表格或元素按文本,数字或日期排序的方法
2015/05/26 Javascript
理解js回收机制通俗易懂版
2016/02/29 Javascript
BootStrap 图标icon符号图标glyphicons不正常显示的快速解决办法
2016/12/08 Javascript
jQuery插件FusionWidgets实现的Cylinder图效果示例【附demo源码】
2017/03/23 jQuery
微信浏览器禁止页面下拉查看网址实例详解
2017/06/28 Javascript
javascript 缓冲运动框架的实现
2017/09/29 Javascript
JS设计模式之数据访问对象模式的实例讲解
2017/09/30 Javascript
js用类封装pop弹窗组件
2017/10/08 Javascript
JS实现去除数组中重复json的方法示例
2017/12/21 Javascript
Vue实现动态添加或者删除对象和对象数组的操作方法
2018/09/21 Javascript
koa2使用ejs和nunjucks作为模板引擎的使用
2018/11/27 Javascript
mongodb初始化并使用node.js实现mongodb操作封装方法
2019/04/02 Javascript
解决vue-router 切换tab标签关闭时缓存问题
2020/07/22 Javascript
python音频处理用到的操作的示例代码
2017/10/27 Python
Python基于列表模拟堆栈和队列功能示例
2018/01/05 Python
python之DataFrame实现excel合并单元格
2021/02/22 Python
详解Python下载图片并保存本地的两种方式
2019/05/15 Python
Python切图九宫格的实现方法
2019/10/10 Python
pytorch:torch.mm()和torch.matmul()的使用
2019/12/27 Python
Python socket处理client连接过程解析
2020/03/18 Python
Django 实现 Websocket 广播、点对点发送消息的代码
2020/06/03 Python
H5混合开发app如何升级的方法
2018/01/10 HTML / CSS
美国家具网站:Cymax
2016/09/17 全球购物
美国在线眼镜商城:Eyeglasses.com
2017/06/26 全球购物
实习生个人的自我评价
2013/12/08 职场文书
党员学习群众路线教育实践活动对照检查材料
2014/09/23 职场文书
收入及婚姻状况证明
2014/11/20 职场文书
服务明星事迹材料
2014/12/29 职场文书
2016教师给学生的毕业寄语
2015/12/04 职场文书
python 自动化偷懒的四个实用操作
2021/04/11 Python
winserver2019安装软件一直卡在应用程序正在为首次使用做准备
2022/06/10 Servers