用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 相关文章推荐
YUI 读码日记之 YAHOO.util.Dom - Part.1
Mar 22 Javascript
prototype 学习笔记整理
Jul 17 Javascript
javascript里模拟sleep(两种实现方式)
Jan 25 Javascript
javascript实现十六进制颜色值(HEX)和RGB格式相互转换
Jun 20 Javascript
Javascript验证用户输入URL地址是否为空及格式是否正确
Oct 09 Javascript
jquery实现简单的无缝滚动
Apr 15 Javascript
js闭包实现按秒计数
Apr 23 Javascript
javascript 开发之百度地图使用到的js函数整理
May 19 Javascript
深入理解Vue transition源码分析
Jul 30 Javascript
利用JS判断客户端类型你应该知道的四种方法
Dec 22 Javascript
Vue2 模板template的四种写法总结
Feb 23 Javascript
优雅地使用loading(推荐)
Apr 20 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&java(三)
2006/10/09 PHP
shopex主机报错误请求解决方案(No such file or directory)
2011/12/27 PHP
简单实用的PHP防注入类实例
2014/12/05 PHP
PHP实现的一致性哈希算法完整实例
2015/11/14 PHP
Yii框架中用response保存cookie,用request读取cookie的原理解析
2019/09/04 PHP
Javascript小技巧之生成html元素
2014/05/15 Javascript
JS实现页面超时后自动跳转到登陆页面
2015/01/19 Javascript
JS+CSS实现类似QQ好友及黑名单效果的树型菜单
2015/09/22 Javascript
编写高性能Javascript代码的N条建议
2015/10/12 Javascript
初步使用bootstrap快速创建页面
2016/03/03 Javascript
JS简单实现仿百度控制台输出信息效果
2016/09/04 Javascript
微信小程序 wx.request(object) API详解及实例代码
2016/09/30 Javascript
BootStrap 可编辑表Table格
2016/11/24 Javascript
fullCalendar中文API官方文档
2017/02/07 Javascript
vue mixins组件复用的几种方式(小结)
2017/09/06 Javascript
详解vue axios二次封装
2018/07/22 Javascript
Vue 组件注册实例详解
2019/02/23 Javascript
关于vue组件事件属性穿透详解
2019/10/28 Javascript
JavaScript接口实现方法实例分析
2020/05/16 Javascript
Vuex中的Mutations的具体使用方法
2020/06/01 Javascript
[15:28]DOTA2 HEROS教学视频教你分分钟做大人-剧毒术士
2014/06/13 DOTA
python中利用await关键字如何等待Future对象完成详解
2017/09/07 Python
Flask 上传自定义头像的实例详解
2020/01/09 Python
tensorflow pb to tflite 精度下降详解
2020/05/25 Python
使用OpenCV实现道路车辆计数的使用方法
2020/07/15 Python
html5之Canvas路径绘图、坐标变换应用实例
2012/12/26 HTML / CSS
百思买加拿大:Best Buy Canada
2018/03/20 全球购物
外语系毕业生找工作的求职信
2013/11/28 职场文书
中年人生感言
2014/02/04 职场文书
渔夫的故事教学反思
2014/02/14 职场文书
入党介绍人评语
2014/05/06 职场文书
物理学专业自荐信
2014/06/11 职场文书
寒暑假实习证明书模板
2014/11/29 职场文书
给老婆道歉的话
2015/01/20 职场文书
HTML+CSS 实现顶部导航栏菜单制作
2021/06/03 HTML / CSS
使用CSS实现一个搜索引擎的原理解析
2021/09/25 HTML / CSS