用NODE.JS中的流编写工具是要注意的事项


Posted in Javascript onMarch 01, 2016

Node.js中的流十分强大,它对处理潜在的大文件提供了支持,也抽象了一些场景下的数据处理和传递。正因为它如此好用,所以在实战中我们常常基于它来编写一些工具 函数/库 ,但往往又由于自己对流的某些特性的疏忽,导致写出的 函数/库 在一些情况会达不到想要的效果,或者埋下一些隐藏的地雷。本文将会提供两个在编写基于流的工具时,私以为有些用的两个tips。

一,警惕EVENTEMITTER内存泄露

在一个可能被多次调用的函数中,如果需要给流添加事件监听器来执行某些操作。那么则需要警惕添加监听器而导致的内存泄露:

'use strict';
const fs = require('fs');
const co = require('co');

function getSomeDataFromStream (stream) {
 let data = stream.read();
 if (data) return Promise.resolve(data);

 if (!stream.readable) return Promise.resolve(null);

 return new Promise((resolve, reject) => {
  stream.once('readable', () => resolve(stream.read()));
  stream.on('error', reject);
  stream.on('end', resolve);
 })
}

let stream = fs.createReadStream('/Path/to/a/big/file');

co(function *() {
 let chunk;
 while ((chunk = yield getSomeDataFromStream(stream)) !== null) {
  console.log(chunk);
 }
}).catch(console.error);

在上述代码中,getSomeDataFromStream函数会在通过监听error事件和end事件,来在流报错或没有数据时,完成这个Promise。然而在执行代码时,我们很快就会在控制台中看到报警信息:(node) warning: possible EventEmitter memory leak detected. 11 error listeners added. Use emitter.setMaxListeners() to increase limit.,因为我们在每次调用该函数时,都为传入的流添加了一个额外的error事件监听器和end事件监听器。为了避免这种潜在的内存泄露,我们要确保每次函数执行完毕后,清除所有此次调用添加的额外监听器,保持函数无污染:

function getSomeDataFromStream (stream) {
 let data = stream.read();
 if (data) return Promise.resolve(data);

 if (!stream.readable) return Promise.resolve(null);

 return new Promise((resolve, reject) => {
  stream.once('readable', onData);
  stream.on('error', onError);
  stream.on('end', done);

  function onData () {
   done();
   resolve(stream.read());
  }

  function onError (err) {
   done();
   reject(err);
  }

  function done () {
   stream.removeListener('readable', onData);
   stream.removeListener('error', onError);
   stream.removeListener('end', done);
  }
 })
}

二,保证工具函数的回调在处理完毕数据后才被调用

工具函数往往会对外提供一个回调函数参数,待处理完流中的所有数据后,带着指定值触发,通常的做法是将回调函数的调用挂在流的end事件中,但如果处理函数是耗时的异步操作,回调函数则可能在所有数据处理完毕前被调用:

'use strict';
const fs = require('fs');

let stream = fs.createReadStream('/Path/to/a/big/file');

function processSomeData (stream, callback) {
 stream.on('data', (data) => {
  // 对数据进行一些异步耗时操作
  setTimeout(() => console.log(data), 2000);
 });

 stream.on('end', () => {
  // ...
  callback()
 })
}

processSomeData(stream, () => console.log('end'));

以上的代码callback回调可能会在数据并未被全部处理时就被调用,因为流的end事件的触发时机仅仅是在流中的数据被读完时。所以我们需要额外地对数据是否已处理完进行检查:

function processSomeData (stream, callback) {
 let count = 0;
 let finished = 0;
 let isEnd = false;

 stream.on('data', (data) => {
  count++;
  // 对数据进行一些异步耗时操作
  setTimeout(() => {
   console.log(data);
   finished++;
   check();
  }, 2000);
 });

 stream.on('end', () => {
  isEnd = true;
  // ...
  check();
 })

 function check () {
  if (count === finished && isEnd) callback()
 }
}

这样一来,回调便会在所有数据都处理完毕后触发了。

Javascript 相关文章推荐
JavaScript 常用函数库详解
Oct 21 Javascript
web css实现整站样式互相切换
Oct 29 Javascript
ie8本地图片上传预览示例代码
Jan 12 Javascript
浅析2种JavaScript继承方式
Dec 04 Javascript
javascript实现html页面之间参数传递的四种方法实例分析
Dec 15 Javascript
svg动画之动态描边效果
Feb 22 Javascript
完美解决axios在ie下的兼容性问题
Mar 05 Javascript
通过vue-router懒加载解决首次加载时资源过多导致的速度缓慢问题
Apr 08 Javascript
详解vantUI框架在vue项目中的应用踩坑
Dec 06 Javascript
通过实例讲解JS如何防抖动
Jun 15 Javascript
layui使用templet格式化表格数据的方法
Sep 16 Javascript
js实现烟花特效
Mar 02 Javascript
JS实现图片平面旋转的方法
Mar 01 #Javascript
JS显示日历和天气的方法
Mar 01 #Javascript
jQuery使用模式窗口实现在主页面和子页面中互相传值的方法
Mar 01 #Javascript
jQuery获取某天的农历日期并判断是否除夕或新年的方法
Mar 01 #Javascript
jQuery实现获取table表格第一列值的方法
Mar 01 #Javascript
JavaScript Date对象详解
Mar 01 #Javascript
JavaScript通过使用onerror设置默认图像显示代替alt
Mar 01 #Javascript
You might like
PHP开发大型项目的一点经验
2006/10/09 PHP
PHP 类型转换函数intval
2009/06/20 PHP
php学习之数据类型之间的转换代码
2011/05/29 PHP
php错误提示failed to open stream: HTTP request failed!的完美解决方法
2011/06/06 PHP
PHP写的求多项式导数的函数代码
2012/07/04 PHP
Laravel 中获取上一篇和下一篇数据
2015/07/27 PHP
php+js实现裁剪任意形状图片
2018/10/31 PHP
JS定时关闭窗口的实例
2013/05/22 Javascript
利用JS判断用户是否上网(连接网络)
2013/12/23 Javascript
extjs 分页使用jsp传递数据示例
2014/07/29 Javascript
JS实现当前页居中分页效果的方法
2015/06/18 Javascript
jQuery+CSS实现滑动的标签分栏切换效果
2015/12/17 Javascript
Javascript的表单与验证-非空验证
2016/03/18 Javascript
浅谈js控制li标签排序问题 js调用php函数的方法
2016/10/16 Javascript
javascript实现鼠标点击页面 移动DIV
2016/12/02 Javascript
jQuery实现浏览器之间跳转并传递参数功能【支持中文字符】
2018/03/28 jQuery
JavaScript深拷贝和浅拷贝概念与用法实例分析
2018/06/07 Javascript
聊聊鉴权那些事(推荐)
2019/08/22 Javascript
深入理解令牌认证机制(token)
2019/08/22 Javascript
vue父子组件间引用之$parent、$children
2020/05/20 Javascript
wepy--用vantUI 实现上弹列表并选择相应的值操作
2020/11/03 Javascript
探寻python多线程ctrl+c退出问题解决方案
2014/10/23 Python
Python自动化运维和部署项目工具Fabric使用实例
2016/09/18 Python
使用python3+xlrd解析Excel的实例
2018/05/04 Python
python3调用百度翻译API实现实时翻译
2018/08/16 Python
Python实现将字符串的首字母变为大写,其余都变为小写的方法
2019/06/11 Python
python画微信表情符的实例代码
2019/10/09 Python
Python ATM功能实现代码实例
2020/03/19 Python
Foot Locker英国官网:美国知名运动产品零售商
2019/02/21 全球购物
聚网科技C++面试笔试题
2015/09/01 面试题
行政管理毕业生自荐信
2014/02/24 职场文书
师范学院毕业生求职信
2014/06/24 职场文书
运动会加油稿20字
2014/11/15 职场文书
幼儿教师年度个人总结
2015/02/05 职场文书
聘任通知书
2015/09/21 职场文书
Innodb存储引擎中的后台线程详解
2022/04/03 MySQL