深入浅析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 相关文章推荐
nodejs分页类代码分享
Jun 17 NodeJs
Nodejs学习笔记之Stream模块
Jan 13 NodeJs
使用Angular和Nodejs、socket.io搭建聊天室及多人聊天室
Aug 21 NodeJs
NodeJS实现阿里大鱼短信通知发送
Jan 17 NodeJs
解析NodeJs的调试方法
Dec 11 NodeJs
nodeJS(express4.x)+vue(vue-cli)构建前后端分离实例(带跨域)
Jul 05 NodeJs
详解NODEJS基于FFMPEG视频推流测试
Nov 17 NodeJs
nodejs实现截取上传视频中一帧作为预览图片
Dec 10 NodeJs
Nodejs连接mysql并实现增、删、改、查操作的方法详解
Jan 04 NodeJs
NodeJs搭建本地服务器之使用手机访问的实例讲解
May 12 NodeJs
Nodejs监控事件循环异常示例详解
Sep 22 NodeJs
Nodejs实现微信分账的示例代码
Jan 19 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
分享下php5类中三种数据类型的区别
2015/01/26 PHP
Yii框架实现对数据库的CURD操作示例
2019/09/03 PHP
php7 list()、session及其他模块的修改实例分析
2020/05/25 PHP
对JavaScript的eval()中使用函数的进一步讨论
2008/07/26 Javascript
jquery使用ColorBox弹出图片组浏览层实例演示
2013/03/14 Javascript
javaScript 计算两个日期的天数相差(示例代码)
2013/12/27 Javascript
JavaScript获得页面base标签中url的方法
2015/04/03 Javascript
JavaScript中用于四舍五入的Math.round()方法讲解
2015/06/15 Javascript
jQuery事件委托之Safari
2016/07/05 Javascript
购物车前端开发(jQuery和bootstrap3)
2016/08/27 Javascript
Angular下H5上传图片的方法(可多张上传)
2017/01/09 Javascript
百度地图JavascriptApi Marker平滑移动及车头指向行径方向
2017/03/13 Javascript
学习使用Bootstrap栅格系统
2017/05/11 Javascript
JavaScript 巧学巧用
2017/05/23 Javascript
微信小程序 POST请求的实例详解
2017/09/29 Javascript
php 解压zip压缩包内容到指定目录的实例
2018/01/23 Javascript
记一次webpack3升级webpack4的踩坑经历
2018/06/12 Javascript
JS实现的tab页切换效果完整示例
2018/12/18 Javascript
跟老齐学Python之大话题小函数(1)
2014/10/10 Python
python获取当前计算机cpu数量的方法
2015/04/18 Python
python+django加载静态网页模板解析
2017/12/12 Python
Python实现发送与接收邮件的方法详解
2018/03/28 Python
python打包成so文件过程解析
2019/09/28 Python
使用批处理脚本自动生成并上传NuGet包(操作方法)
2019/11/19 Python
在python3中使用shuffle函数要注意的地方
2020/02/28 Python
python中for in的用法详解
2020/04/17 Python
六种酷炫Python运行进度条效果的实现代码
2020/07/17 Python
New Balance比利时官方网站:购买鞋子和服装
2021/01/15 全球购物
小学庆六一活动方案
2014/02/28 职场文书
小学生手册家长评语
2014/04/16 职场文书
2014年五四青年节演讲稿范文
2014/04/22 职场文书
学校领导班子四风问题整改意见
2014/10/02 职场文书
2015年乡镇信访工作总结
2015/04/07 职场文书
竞聘演讲报告:基本写作有哪些?附开头范文
2019/10/16 职场文书
python爬取豆瓣电影TOP250数据
2021/05/23 Python
浅谈如何提高PHP代码质量之单元测试
2021/05/28 PHP