用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分页代码(当前页码居中)
Sep 20 Javascript
jQuery中odd选择器的定义和用法
Dec 23 Javascript
jQuery实现鼠标选文字发新浪微博的方法
Apr 02 Javascript
微信支付 JS API支付接口详解
Jul 11 Javascript
AngularJs  unit-testing(单元测试)详解
Sep 02 Javascript
bootstrap弹出层的多种触发方式
May 10 Javascript
bootstrap table实现x-editable的行单元格编辑及解决数据Empty和支持多样式问题
Aug 10 Javascript
自制简易打赏功能的实例
Sep 02 Javascript
前端MVVM框架解析之双向绑定
Jan 24 Javascript
javascript中UMD规范的代码推演
Aug 29 Javascript
javascript 设计模式之享元模式原理与应用详解
Apr 08 Javascript
详解JS ES6编码规范
May 07 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
用函数读出数据表内容放入二维数组
2006/10/09 PHP
PHP新手上路(八)
2006/10/09 PHP
基于PHP编程注意事项的小结
2013/04/27 PHP
PHP 前加at符合@的作用解析
2015/07/31 PHP
使用PHP进行微信公众平台开发的示例
2015/08/21 PHP
谈谈PHP连接Access数据库的注意事项
2016/08/12 PHP
PHP获取日期对应星期、一周日期、星期开始与结束日期的方法
2018/06/22 PHP
Prototype使用指南之selector.js
2007/01/10 Javascript
JavaScript参数个数可变的函数举例说明
2014/10/10 Javascript
Javascript中实现String.startsWith和endsWith方法
2015/06/10 Javascript
jQuery操作基本控件方法实例分析
2015/12/31 Javascript
利用AJAX实现WordPress中的文章列表及评论的分页功能
2016/05/17 Javascript
Js操作DOM元素及获取浏览器高宽的简单方法
2016/09/08 Javascript
详解Vue学习笔记进阶篇之列表过渡及其他
2017/07/17 Javascript
vue2.0使用swiper组件实现轮播的示例代码
2018/03/03 Javascript
vue-router跳转时打开新页面的两种方法
2019/07/29 Javascript
vue 封装面包屑组件教程
2020/11/16 Javascript
javascript实现电商放大镜效果
2020/11/23 Javascript
用vite搭建vue3应用的实现方法
2021/02/22 Vue.js
Python实现国外赌场热门游戏Craps(双骰子)
2015/03/31 Python
python中的字符串内部换行方法
2018/07/19 Python
Django框架搭建的简易图书信息网站案例
2019/05/25 Python
解决tensorflow模型压缩的问题_踩坑无数,总算搞定
2021/03/02 Python
HTML5 b和i标记将被赋予真正的语义
2009/07/16 HTML / CSS
温泉秘密:Onsen Secret
2020/07/06 全球购物
最新英语专业学生求职信范文
2013/09/21 职场文书
毕业生自荐信格式
2014/03/07 职场文书
进口业务员岗位职责
2014/04/06 职场文书
暑期社会实践心得体会
2014/09/02 职场文书
车辆挂靠协议书
2016/03/23 职场文书
如何才能写好调研报告?
2019/07/03 职场文书
JavaScript实现复选框全选功能
2021/04/11 Javascript
Java框架入门之简单介绍SpringBoot框架
2021/06/18 Java/Android
深入解读Java三大集合之map list set的用法
2021/11/11 Java/Android
教你使用Jenkins集成Harbor自动发布镜像
2022/04/03 Servers
Python线程池与GIL全局锁实现抽奖小案例
2022/04/13 Python