深入浅析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中使用monk访问mongodb
Jul 06 NodeJs
基于NodeJS的前后端分离的思考与实践(一)全栈式开发
Sep 26 NodeJs
轻松创建nodejs服务器(2):nodejs服务器的构成分析
Dec 18 NodeJs
windows 下安装nodejs 环境变量设置
Feb 02 NodeJs
手把手教你如何使用nodejs编写cli命令行
Nov 05 NodeJs
Nodejs对postgresql基本操作的封装方法
Feb 20 NodeJs
nodejs npm错误Error:UNKNOWN:unknown error,mkdir 'D:\Develop\nodejs\node_global'at Error
Mar 02 NodeJs
nodejs微信开发之授权登录+获取用户信息
Mar 17 NodeJs
nodejs微信开发之自动回复的实现
Mar 17 NodeJs
Nodejs核心模块之net和http的使用详解
Apr 02 NodeJs
通过Nodejs搭建网站简单实现注册登录流程
Jun 14 NodeJs
nodejs的安装使用与npm的介绍
Sep 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
PHP strip_tags()去除HTML、XML以及PHP的标签介绍
2014/02/18 PHP
php获取apk包信息的方法
2014/08/15 PHP
php使用pdo连接并查询sql数据库的方法
2014/12/24 PHP
初识laravel5
2015/03/02 PHP
php计算到指定日期还有多少天的方法
2015/04/14 PHP
Laravel中9个不经常用的小技巧汇总
2019/04/16 PHP
Smarty模板变量与调节器实例详解
2019/07/20 PHP
根据IP的地址,区分不同的地区,查看不同的网站页面的js代码
2013/02/26 Javascript
js下拉菜单语言选项简单实现
2013/09/23 Javascript
Javascript玩转继承(一)
2014/05/08 Javascript
浅谈利用JavaScript进行的DDoS攻击原理与防御
2015/06/04 Javascript
jquery显示隐藏元素的实现代码
2016/05/19 Javascript
vue-router相关基础知识及工作原理
2018/03/16 Javascript
使用gulp构建前端自动化的方法示例
2018/12/25 Javascript
node微信开发之获取access_token+自定义菜单
2019/03/17 Javascript
vue项目中将element-ui table表格写成组件的实现代码
2019/06/12 Javascript
创建nuxt.js项目流程图解
2020/03/13 Javascript
vue中解决拖拽改变存在iframe的div大小时卡顿问题
2020/07/22 Javascript
[01:15:00]LGD vs Mineski Supermajor 胜者组 BO3 第一场 6.5
2018/06/06 DOTA
[57:31]DOTA2-DPC中国联赛 正赛 SAG vs CDEC BO3 第一场 2月1日
2021/03/11 DOTA
Python 的 with 语句详解
2014/06/13 Python
python使用Tkinter显示网络图片的方法
2015/04/24 Python
Python映射拆分操作符用法实例
2015/05/19 Python
Python学习小技巧之列表项的排序
2017/05/20 Python
Python基于更相减损术实现求解最大公约数的方法
2018/04/04 Python
与Django结合利用模型对上传图片预测的实例详解
2019/08/07 Python
Python定时任务APScheduler安装及使用解析
2020/08/07 Python
CSS3 icon font完全指南(CSS3 font 会取代icon图标)
2013/01/06 HTML / CSS
HTML5 canvas基本绘图之绘制矩形
2016/06/27 HTML / CSS
wordpress添加Html5的表单验证required方法小结
2020/08/18 HTML / CSS
澳大利亚首屈一指的在线购物目的地:Kogan.com
2017/02/02 全球购物
英国领先的在线鱼贩:The Fish Society
2020/08/12 全球购物
SQL Server 2000数据库的文件有哪些,分别进行描述。
2015/11/09 面试题
最美孝心少年事迹材料
2014/08/15 职场文书
草房子读书笔记
2015/06/29 职场文书
2015年教学副校长工作总结
2015/07/22 职场文书