用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的原生方法获取数组中的最大(最小)值
Dec 19 Javascript
Javascript 中 null、NaN和undefined的区别总结
Apr 10 Javascript
javascript简单事件处理和with用法介绍
Sep 16 Javascript
javascript禁用Tab键脚本实例
Nov 22 Javascript
javascript实现修改微信分享的标题内容等
Dec 11 Javascript
判断横屏竖屏(三种)
Feb 13 Javascript
vue监听滚动事件实现滚动监听
Apr 11 Javascript
Node.JS使用Sequelize操作MySQL的示例代码
Oct 09 Javascript
vue 注册组件的使用详解
May 05 Javascript
微信小程序新手教程之页面打开数量限制
Mar 03 Javascript
在博客园博文中添加自定义右键菜单的方法详解
Feb 05 Javascript
使用Webpack 搭建 Vue3 开发环境过程详解
Jul 28 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
动态生成gif格式的图像要注意?
2006/10/09 PHP
学习使用curl采集curl使用方法
2012/01/11 PHP
JavaScript 事件参考手册
2008/12/24 Javascript
JavaScript实现下拉菜单的显示和隐藏
2016/01/05 Javascript
javascript实现获取图片大小及图片等比缩放的方法
2016/11/24 Javascript
jQuery实现字符串全部替换的方法
2016/12/12 Javascript
BootStrap select2 动态改变值的方法
2017/02/10 Javascript
Bootstrap 3浏览器兼容性问题及解决方案
2017/04/11 Javascript
element上传组件循环引用及简单时间倒计时的实现
2018/10/01 Javascript
对node通过fs模块判断文件是否是文件夹的实例讲解
2019/06/10 Javascript
Layui数据表格跳转到指定页的实现方法
2019/09/05 Javascript
解决layer 关闭当前弹窗 关闭遮罩层 input值获取不到的问题
2019/09/25 Javascript
使用python编写批量卸载手机中安装的android应用脚本
2014/07/21 Python
python错误处理详解
2014/09/28 Python
python自动化测试之连接几组测试包实例
2014/09/28 Python
Python入门_浅谈逻辑判断与运算符
2017/05/16 Python
Java编程迭代地删除文件夹及其下的所有文件实例
2018/02/10 Python
NLTK 3.2.4 环境搭建教程
2018/09/19 Python
利用nohup来开启python文件的方法
2019/01/14 Python
python3.x实现base64加密和解密
2019/03/28 Python
pytorch 模型可视化的例子
2019/08/17 Python
使用PyInstaller将Pygame库编写的小游戏程序打包为exe文件及出现问题解决方法
2019/09/06 Python
Python日志处理模块logging用法解析
2020/05/19 Python
Python用K-means聚类算法进行客户分群的实现
2020/08/23 Python
美国最大的宠物药店:1-800-PetMeds
2016/10/02 全球购物
写一个函数,求一个字符串的长度。在main函数中输入字符串,并输出其长度
2015/11/18 面试题
Linux面试经常问的文件系统操作命令
2015/11/05 面试题
数控专业推荐信范文
2013/12/02 职场文书
小学生优秀评语大全
2014/04/22 职场文书
职位说明书范文
2014/05/07 职场文书
安全责任书模板
2014/07/22 职场文书
学生会辞职信
2015/03/02 职场文书
2015年上半年信访工作总结
2015/03/30 职场文书
Mysql Online DDL的使用详解
2021/05/20 MySQL
python实现对doc、txt、xls等文档的读写操作
2022/04/02 Python
使用HBuilder制作一个简单的HTML5网页
2022/07/07 HTML / CSS