深入浅析NodeJs并发异步的回调处理


Posted in NodeJs onDecember 21, 2015

这里说并发异步,并不准确,应该说连续异步。NodeJs单线程异步的特性,直接导致多个异步同时进行时,无法确定最后的执行结果来回调。举个简单的例子:

for(var i = 0; i < 5; i++) {
  fs.readFile('file', 'utf-8', function(error, data){});
}

连续发起了5次读文件的异步操作,很简单,那么问题来了,我怎么确定所有异步都执行完了呢?因为要在它们都执行完后,才能进行之后的操作。相信有点经验的同学都会想到使用记数的方式来进行,但如何保证记数正确又是一个问题。仔细想想:

回调是一个函数,每个异步操作时将计数器+1,当每个异步结束时将计数器-1,通过判断计数器是否为0来确定是否执行回调。这个逻辑很简单,需要一个相对于执行时和回调时的全局变量作为计数器,而且要在传给异步方法是执行+1的操作,而且之后将返回一个用来回调的函数,有点绕,不过看看Js函数的高级用法:

var pending = (function() {
  var count = 0;
  return function() {
    count++;
    return function() {
      count--;
      if (count === 0) {
        // 全部执行完毕
      }
    }
  }
});

当pending调用时,即pending(),比如:

var done = pending();

这时计数变量count即被初始化为0,返回的函数附给了done,这时如果执行done(),会是什么?是不是直接执行pending返回的第一个函数,即:pending()(),这个执行又是什么,首先将计数变量count+1,又返回了一个函数,这个函数直接当做callback传给异步的方法,当执行这个callback的时候,首先是将计数变量count-1,再判断count是否为0,如果为0即表示所有的异步执行完成了,从而达到连续的异步,同一回调的操作。

关键就在两个return上,简单的说:

第一个return的函数是将count+1,接着返回需要回调的函数

第二个return的函数就是需要回调的函数,如果它执行,就是将count-1,然后判断异步是否全部执行完成,完成了,就回调

看个实际点的例子,读取多个文件的异步回调:

var fileName = ['1.html', '2.html', '3.html'];
var done = pending(function(fileData) {
  console.log('done');
  console.log(fielData);
});
for(var i = 0; i < fileName.lenght; i++) {
  fs.readFile(fileName[i], 'utf-8', done(fileName[i]));
}

其中的done,即用pending方法包起了我们想回调执行的方法,当计数器为0时,就会执行它,那我们得改进一下pending方法:

var pending = (function(callback) {
  var count = 0;
  var returns = {};
  console.log(count);
  return function(key) {
    count++;
    console.log(count);
    return function(error, data) {
      count--;
      console.log(count);
      returns[key] = data;
      if (count === 0) {
        callback(returns);
      }
    }
  }
});

callback即为我们的回调函数,当var done = pending(callback)时,done其实已为第一个return的函数,它有一个参数,可以当做返回的值的下标,所以在循环体中done(fileName[i]),把文件名传了进去。这个done()是直接执行的,它将count+1后,返回了要传给异步方法的回调函数,如前面所说,这个回调函数里会根据计数变量来判断是否执行我们希望执行的回调函数,而且把文件的内容传给了它,即returns。好了,运行一下,相信能够准确的看到运行结果。

0
1
2
3
2
1
0
done
{"1.html": "xxx", "2.html": "xxx", "3.html": "xxx"}

从计数上明显能看出,从0-3再到0,之后就是我们的回调函数输出了done和文件的内容。

这个问题解决了,我们要思考一下,如何让这样的方法封装重用,不然,每次都写pending不是很不科学吗?

下面看看UnJs(我的一个基于NodeJs的Web开发框架)的处理方式,应用于模板解析中的子模板操作:

unjs.asyncSeries = function(task, func, callback) {
  var taskLen = task.length;
  if (taskLen <= 0) {
    return;
  }
  var done = unjs.pending(callback);
  for(var i = 0; i < taskLen; i++) {
    func(task[i], done);
  }
}

asyncSeries有三个参数,意思是:

task: 需要处理的对象,比如需要读取的文件,它是一个列表,如果不是列表,或列表长度为0,它将不会执行

func: 异步方法,比如fs.readFile,就是通过它传进去的

callback: 我们希望回调的方法

done和前面同理,它传给了func,但并没有执行,因为希望应用端能可控制参数,所以让应用端去执行。

再看看处理子模板时的操作:

var subTemplate = [];
var patt = /\{\% include \'(.+)\' \%\}/ig;
while(sub = patt.exec(data)) {
  var subs = sub;
  subTemplate.push([subs[0], subs[1]]);
}
unjs.asyncSeries(subTemplate, function(item, callback) {
  fs.readFile('./template/' + item[1], 'utf-8', callback(item[0]));
}, function(data) {
  for(var key in data) {
    html = html.replace(key, data[key]);
  }
});

subTemplate这个列表,是根据对子模板的解析生成的数据,它是一个二维的数组,每个子项的第一个值为子模板的调用文本,即:{% include 'header.html' %}这样的字符串,第二个参数为子模板文件名,即:header.html

asyncSeries的第二个参数是的callback,实际上是第三个参数,也就是我们希望执行的回调函数经过pending处理的回调方法,如前面所说,在asyncSeries内部,它并没有运行,而是到这里运行的,即:callback(item[0]),带上了参数,因为后面还要根据这个参数将父模板中调用子模板的字符串替换为对应子模板的内容。

这样子,只要需要连续异步时,就可以使用asyncSeries方法来处理了。因为异步的关系,程序的流程有点绕,可能开始不太好理解,即使熟悉了,也有可能突然想不明白,没关系,比如,第二个参数中的callback实际是第三个参数生成的,开始可能你就会想,这个callback倒底是啥。还有就是pending的两个return,也是不太好理解的,需要多想想。

好了,连续异步的回调使用Js函数的高级特性完成了。但NodeJs的异步性着实让程序的控制很成问题,诸如还有连续异步,但要传值的操作等,这些都是可以通过这样的思路,变化一下即可实现的。

以上内容是小编给大家分享的NodeJs并发异步的回调处理的相关知识,希望大家喜欢。

NodeJs 相关文章推荐
基于 Docker 开发 NodeJS 应用
Jul 30 NodeJs
Nodejs全栈框架StrongLoop推荐
Nov 09 NodeJs
nodejs实现的一个简单聊天室功能分享
Dec 06 NodeJs
基于Nodejs利用socket.io实现多人聊天室
Feb 22 NodeJs
Nodejs实现多房间简易聊天室功能
Jun 20 NodeJs
nodejs对express中next函数的一些理解
Sep 08 NodeJs
详解使用PM2管理nodejs进程
Oct 24 NodeJs
Nodejs中怎么实现函数的串行执行
Mar 02 NodeJs
nodejs的安装使用与npm的介绍
Sep 11 NodeJs
nodejs dgram模块广播+组播的实现示例
Nov 04 NodeJs
在NodeJs中使用node-schedule增加定时器任务的方法
Jun 08 NodeJs
一文秒懂nodejs中的异步编程
Jan 28 NodeJs
基于nodejs+express(4.x+)实现文件上传功能
Nov 23 #NodeJs
Nodejs Express4.x开发框架随手笔记
Nov 23 #NodeJs
Nodejs的express使用教程
Nov 23 #NodeJs
nodejs初步体验篇
Nov 23 #NodeJs
Nodejs初级阶段之express
Nov 23 #NodeJs
基于html5和nodejs相结合实现websocket即使通讯
Nov 19 #NodeJs
浅析nodejs实现Websocket的数据接收与发送
Nov 19 #NodeJs
You might like
sae使用smarty模板的方法
2013/12/17 PHP
php数据库备份还原类分享
2014/03/20 PHP
PHP微信开发之查询微信精选文章
2016/06/23 PHP
购物车实现的几种方式优缺点对比
2018/05/02 PHP
JavaScript replace(rgExp,fn)正则替换的用法
2010/03/04 Javascript
Jquery颜色选择器ColorPicker实现代码
2012/11/14 Javascript
javascript 获取模态窗口的滚动位置代码
2013/08/06 Javascript
js读取被点击次数的简单实例(从数据库中读取)
2014/03/07 Javascript
关于JavaScript的变量的数据类型的判断方法
2015/08/14 Javascript
JS实现Select的option上下移动的方法
2016/03/01 Javascript
jQuery表格插件datatables用法汇总
2016/03/29 Javascript
对Angular.js Controller如何进行单元测试
2016/10/25 Javascript
基于zepto.js实现手机相册功能
2017/07/11 Javascript
使用js获取伪元素的content实例
2017/10/24 Javascript
Django+Vue跨域环境配置详解
2018/07/06 Javascript
vue实现重置表单信息为空的方法
2018/09/29 Javascript
[33:39]DOTA2上海特级锦标赛C组小组赛#2 LGD VS Newbee第二局
2016/02/27 DOTA
[01:57]2018年度DOTA2最具潜力解说-完美盛典
2018/12/16 DOTA
python中xrange用法分析
2015/04/15 Python
python实现对求解最长回文子串的动态规划算法
2018/06/02 Python
python3+selenium实现126邮箱登陆并发送邮件功能
2019/01/23 Python
Python面向对象程序设计中类的定义、实例化、封装及私有变量/方法详解
2019/02/28 Python
详解python和matlab的优势与区别
2019/06/28 Python
使用python修改文件并立即写回到原始位置操作(inplace读写)
2020/06/28 Python
python爬取代理IP并进行有效的IP测试实现
2020/10/09 Python
python爬虫爬取淘宝商品比价(附淘宝反爬虫机制解决小办法)
2020/12/03 Python
地图可视化神器kepler.gl python接口的使用方法
2020/12/22 Python
基于CSS3特效之动画:animation的应用
2013/05/09 HTML / CSS
canvas拼图功能实现代码示例
2018/11/21 HTML / CSS
男女时尚与复古风格在线购物:RoseGal(全球免费送货)
2017/07/19 全球购物
Booking.com荷兰:全球酒店网上预订
2017/08/22 全球购物
荷兰家电购物网站:Expert.nl
2020/01/18 全球购物
小学生安全演讲稿
2014/04/25 职场文书
贷款担保申请书
2014/05/20 职场文书
法学院毕业生求职信
2014/06/25 职场文书
2015年普法依法治理工作总结
2015/05/26 职场文书