用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 相关文章推荐
在IE,Firefox,Safari,Chrome,Opera浏览器上调试javascript
Dec 02 Javascript
jQuery学习笔记之jQuery选择器的使用
Dec 22 Javascript
JavaScript中constructor()方法的使用简介
Jun 05 Javascript
详解JavaScript的回调函数
Nov 20 Javascript
javascript图片切换综合实例(循环切换、顺序切换)
Jan 13 Javascript
js获取html的span标签的值方法(超简单)
Jul 26 Javascript
JS克隆,属性,数组,对象,函数实例分析
Nov 26 Javascript
vue-cli 引入、配置axios的方法
May 08 Javascript
vue实现路由切换改变title功能
May 28 Javascript
layui自己添加图片按钮并点击跳转页面的例子
Sep 14 Javascript
angular中的post请求处理示例详解
Jun 30 Javascript
JavaScript 防抖和节流遇见的奇怪问题及解决
Nov 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防止CC攻击代码 php防止网页频繁刷新
2015/12/21 PHP
PHP数组生成XML格式数据的封装类实例
2016/11/10 PHP
php实现替换手机号中间数字为*号及隐藏IP最后几位的方法
2016/11/16 PHP
PHP日志LOG类定义与用法示例
2018/09/06 PHP
PHP抽象类基本用法示例
2018/12/28 PHP
jQuery查询数据返回object和字符串影响原因是什么
2013/08/09 Javascript
js跨域问题浅析及解决方法优缺点对比
2014/11/08 Javascript
jquery+easeing实现仿flash的载入动画
2015/03/10 Javascript
jquery中map函数遍历数组用法实例
2015/05/18 Javascript
jQuery实现响应鼠标背景变化的动态菜单效果代码
2015/08/27 Javascript
jquery Deferred 快速解决异步回调的问题
2016/04/05 Javascript
JS获取鼠标相对位置的方法
2016/09/20 Javascript
微信小程序入门教程
2016/11/18 Javascript
vue项目中做编辑功能传递数据时遇到问题的解决方法
2016/12/19 Javascript
创建一般js对象的几种方式
2017/01/19 Javascript
JS中的回调函数实例浅析
2018/03/21 Javascript
JavaScript 下载svg图片为png格式
2018/06/21 Javascript
详解KOA2如何手写中间件(装饰器模式)
2018/10/11 Javascript
vue iview多张图片大图预览、缩放翻转
2019/07/13 Javascript
微信小程序使用自定义组件导航实现当前页面高亮
2020/01/02 Javascript
Python的函数嵌套的使用方法
2014/01/24 Python
PyQt5每天必学之带有标签的复选框
2018/04/19 Python
删除DataFrame中值全为NaN或者包含有NaN的列或行方法
2018/11/06 Python
Python OpenCV图像指定区域裁剪的实现
2019/10/30 Python
python logging模块的使用
2020/09/07 Python
Pytorch生成随机数Tensor的方法汇总
2020/09/09 Python
Python 随机按键模拟2小时
2020/12/30 Python
使用OpenCV实现人脸图像卡通化的示例代码
2021/01/15 Python
详解如何通过H5(浏览器/WebView/其他)唤起本地app
2017/12/11 HTML / CSS
澳大利亚领先的男装零售连锁店:Lowes
2020/08/07 全球购物
个人职业及收入证明
2014/10/13 职场文书
工程承包协议书
2014/10/20 职场文书
幽默导游词开场白
2015/05/29 职场文书
卡特教练观后感
2015/06/08 职场文书
致运动员的广播稿
2015/08/19 职场文书
golang中字符串MD5生成方式总结
2021/07/04 Golang