深入浅析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 相关文章推荐
golang、python、php、c++、c、java、Nodejs性能对比
Mar 12 NodeJs
跟我学Nodejs(一)--- Node.js简介及安装开发环境
May 20 NodeJs
快速掌握Node.js之Window下配置NodeJs环境
Mar 21 NodeJs
Nodejs进阶:基于express+multer的文件上传实例
Nov 21 NodeJs
nodejs实现发出蜂鸣声音(系统报警声)的方法
Jan 18 NodeJs
angular2+nodejs实现图片上传功能
Mar 27 NodeJs
详解NODEJS的http实现
Jan 04 NodeJs
nodejs使用http模块发送get与post请求的方法示例
Jan 08 NodeJs
NodeJS安装图文教程
Apr 19 NodeJs
nodejs更新package.json中的dependencies依赖到最新版本的方法
Oct 10 NodeJs
图解NodeJS实现登录注册功能
Sep 16 NodeJs
NodeJS模块Buffer原理及使用方法解析
Nov 11 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
搜索引擎技术核心揭密
2006/10/09 PHP
百度实时推送api接口应用示例
2014/10/21 PHP
php使用post数组的键值创建同名变量并赋值的方法
2015/04/03 PHP
浅谈PHP中output_buffering
2015/07/13 PHP
php用户密码加密算法分析【Discuz加密算法】
2016/10/12 PHP
PHP的curl函数的用法总结
2019/02/14 PHP
数据结构之利用PHP实现二分搜索树
2020/10/25 PHP
js实时获取并显示当前时间的方法
2015/07/31 Javascript
原生JavaScript实现异步多文件上传
2015/12/02 Javascript
JavaScript截取指定长度字符串点击可以展开全部代码
2015/12/04 Javascript
jQuery添加options点击事件并传值实例代码
2016/05/18 Javascript
JavaScript学习笔记整理_简单实现枚举类型,扑克牌应用
2016/09/19 Javascript
JS 实现导航菜单中的二级下拉菜单的几种方式
2016/10/31 Javascript
Three.js基础学习之场景对象
2017/09/27 Javascript
angular5 httpclient的示例实战
2018/03/12 Javascript
Element-UI中Upload上传文件前端缓存处理示例
2019/02/21 Javascript
解决vue项目中页面调用数据 在数据加载完毕之前出现undefined问题
2019/11/14 Javascript
[57:37]EG vs Mineski 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/17 DOTA
[01:04:14]OG vs Winstrike 2018国际邀请赛小组赛BO2 第二场 8.19
2018/08/21 DOTA
[20:39]DOTA2-DPC中国联赛 正赛开幕式 1月18日
2021/03/11 DOTA
python结合opencv实现人脸检测与跟踪
2015/06/08 Python
Python获取指定字符前面的所有字符方法
2018/05/02 Python
Python实现的个人所得税计算器示例
2018/06/01 Python
对Python生成器、装饰器、递归的使用详解
2019/07/19 Python
python识别文字(基于tesseract)代码实例
2019/08/24 Python
python+pygame实现坦克大战
2019/09/10 Python
瑞典领先的汽车零部件网上零售商:bildelaronline24.se
2017/01/12 全球购物
在c#中using和new这两个关键字有什么意义
2013/05/19 面试题
幼教毕业生自我鉴定
2014/01/12 职场文书
大学生求职信范文
2014/05/24 职场文书
夏季药店促销方案
2014/08/22 职场文书
聘任书范文大全
2015/09/21 职场文书
感谢信的技巧及范例
2019/05/15 职场文书
创业计划书之农家乐
2019/10/09 职场文书
自从在 IDEA 中用了热部署神器 JRebel 之后,开发效率提升了 10(真棒)
2021/06/26 Java/Android
MySQL实现字段分割一行转多行的示例代码
2022/07/07 MySQL