NodeJs并发异步的回调处理
这里说并发异步,并不准确,应该说连续异步。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并发异步的回调处理的更多相关文章
- [Effective JavaScript 笔记]第67条:绝不要同步地调用异步的回调函数
设想有downloadAsync函数的一种变种,它持有一个缓存(实现为一个Dict)来避免多次下载同一个文件.在文件已经被缓存的情况下,立即调用回调函数是最优选择. var cache=new Dic ...
- 【转载】游戏并发编程的讨论 & Nodejs并发性讨论 & 语法糖术语
知乎上这篇文章对于游戏后端.性能并发.nodejs及scala等语言的讨论,很好,值得好好看. https://www.zhihu.com/question/21971645 经常了解一些牛逼技术人员 ...
- nodejs基础(回调函数、模块、事件、文件读写、目录的创建与删除)
node官网:http://nodejs.cn/ 今天想看看node的视频,对node进一步了解, 1.我们可以从官网下载node到自己的电脑上,今天了解到node的真正概念,node时javascr ...
- nodejs中处理回调函数的异常
如果是使用nodejs+express3这个经典的组合,那么有一种很方面的处理回调函数异常的方法: 1. 安装模块:express-domain-middleware 2. 加入如下的代码: app. ...
- Ajax异步的回调函数执行了多遍
问题: 在做下拉滚动加载时(类似于qq空间下拉加载),数据向下滚动一次,就会加载一次,即append一下,跟踪js后,发现回调函数执行了多次,导致append将上次的append结果append上了, ...
- 使用redis实现nodejs并发服务
const redisClient = require('redis').createClient(6379, '127.0.0.1'); const crypto = require('crypto ...
- 【Nodejs】nimble或async并不能保证程序串行执行,回调是回避不了的坑
先看一段例程: //------------------------------- // 用于创建目录 //------------------------------- function creat ...
- nodejs处理高并发问题
做了一个nodejs并发测试,先描述一下环境 数据库mysql,大概两张表,读取第一张表test的数据,拿出来-1,存到第二张testlog表记录一下,用jmeter同事模拟50个请求,结果发现, ...
- NodeJS的异步编程风格
NodeJS的异步编程风格 http://www.infoq.com/cn/news/2011/09/nodejs-async-code NodeJS运行环境因其支持Javascript语言和异步编程 ...
随机推荐
- Python正则表达式详解
我用双手成就你的梦想 python正则表达式 ^ 匹配开始 $ 匹配行尾 . 匹配出换行符以外的任何单个字符,使用-m选项允许其匹配换行符也是如此 [...] 匹配括号内任何当个字符(也有或的意思) ...
- java18
1:Map(掌握) (1)将键映射到值的对象.一个映射不能包含重复的键:每个键最多只能映射到一个值. (2)Map和Collection的区别? A:Map 存储的是键值对形式的元素,键唯一,值可以重 ...
- Java通过sessionId获取Session
Servlet2.1之后不支持SessionContext里面getSession(String id)方法. 但是,我们可以通过HttpSessionListener监听器和全局静态map自己实现一 ...
- javaWeb开发中的中文编码问题
常规解决乱码问题的方法是: a.把所有的jsp页面的charset设置为UTF-8. b.添加过滤器,在filter内调用request.setCharacterEncoding("ut ...
- 使用eclipse+fiddler+微信web开发者工具调试本地微信页面
前面已经说了调试服务器上的微信页面,放链接:http://www.cnblogs.com/Gabriel-Wei/p/5977850.html 还有fiddler调试链接:http://www.cnb ...
- scala安装
一:在官网下载相应的版本http://www.scala-lang.org/download/2.10.6.html 二,在linux中解压下载下来的scala包 三:配置环境变量 export ...
- [MVC学习笔记]5.使用Controller来代替Filter完成登录验证(Session校验)
之前的学习中,在对Session校验完成登录验证时,通常使用Filter来处理,方法类似与前文的错误日志过滤,即新建Filter类继承ActionFilterAttribute类,重写On ...
- Ajax验证用户名是否存在模板
Jsp 页面 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8 ...
- 【javascript学习——《javascript高级程序设计》笔记】DOM操作
DOM(文档对象模型)是针对HTML和XML文档的一个API(应用程序编程接口).DOM描绘了一个层次节点树,允许开发人员添加.移除和修改. 1.节点层次 <html> <head& ...
- 修改tomcat服务器banner的方法
对于工作中经常使用tomcat的童鞋来说,已经很习惯地看到tomcat看到下图: 但是在实际场景中,这个Banner给入侵者提供了一定的指示作用.为了安全起见,要求更改这个信息,以起到一定的迷惑作用. ...