众所周知,node与其他后台不同的很大一部分原因在于其异步I/O,异步使得程序运行不会被阻塞,这也是node的性能所在。但是异步编程往往不是那么友好,在此我们为异步编程归纳了一些常用的解决方案,这些方案同样适合在客户端采纳。
1.事件发布/订阅机制
2.Promise/Deferred模式
3.流程控制库
事件发布/订阅模式
事件发布/订阅模式使用很简单,如下所示:
emitter.on("event1",function(message){
alert(message);
});
emitter.emit("event1","Hello Baby");
订阅事件实际上就是一个高阶函数的应用,将回调函数传入一个第三方,然后在合适的时机取出来运行就可以了。可以理解为将原来两者的耦合关系解开,插入一个第三者,原来的两者都同时与这插进来的第三个者耦合。基础实现的代码比较简单,就不写出来了。
*如果对同一个事件添加了超过10个侦听,在node中会得到一条警告,设计者认为可能造成内存泄漏,可以使用emitter.setMaxListeners(0)来将这个限制去掉。
对于事件发布/订阅模式,一个推荐模块是EventProxy模块,该模块提供了all(),tail(),after(),fail(),done()等方法,使用起来十分便捷,详细不再展开。
Promise/Deferred模式
事件发布/订阅模式需要预先定义后续的执行流程,而Promise/Deferred模式则是先执行异步,延迟传递处理的方式。
Promise/A模型中规定了一个Promise对象只要具备then()方法即可,then()方法中接收成功与失败的回调。Promise/Deferred模式实际上也是使用了事件发布/订阅模式来完成的,简单实现如下:
var Promise = function(){
EventEmitter.call(this);
};
util.inherits(Promise,EventEmitter);
Promise.prototype.then = function(successHandler,errorHandler,progressHandler){
if(typeof successHandler === "function"){
this.once("success",successHandler);
}
if(typeof errorHandler === "function"){
this.once("error",errorHandler);
}
if(typeof progressHandler === "function"){
this.once("progress",progressHandler);
}
return this;
};
var Deferred = function(){
this.state = "unSuccess";
this.promise = new Promise();
};
Deferred.prototype.resolve = function(obj){
this.state = "success";
this.promise.emit("success",obj);
};
Deferred.prototype.reject = function(obj){
this.state = "error";
this.promise.emit("error",obj);
};
Deferred.prototype.progress = function(obj){
this.promise.emit("progress",obj);
};
定义一个Promise对象继承node的events模块,在promise原型上定义了then()方法,接收三个参数,分别是成功回调,失败回调和progress回调。在then()方法中利用once注册了三个事件,然后返回this以便链式调用(关于链式调用详细实现建议阅读《深入浅出node.js》异步编程部分)。 定义了一个Deferred对象,在里面定义了状态,同时将Promise对象拿过来以便能用到events提供的api。定义了resolve,reject和progress方法响应在Promise中注册的事件。至于Promise/Deferred模式的使用还是十分简洁明了,就不再赘述了。
流程控制库
最知名的流程控制模块非async莫属,在node开发中,流程控制是最基本的需求。async模块提供了一系列的api来满足这些需求,其内部实现主要是对回调函数的注入。
series()方法实现一组任务的串行执行;parallel()方法实现一组任务并行执行;waterfall方法实现了有依赖的异步串行执行;甚至提供了强大的auto方法根据依赖关系自动分析,以最佳的顺序执行。
简单解释一下其中series()的实现:
async.series([
function(cb){
fs.readFile('file1','utf-8',cb);
},
function(cb){
fs.readFile('file2','utf-8',cb);
}
],function(error,results){
//results => [file1,file2]
});
等同于:
fs.readFile('file1','utf-8',function(err,content){
if(err){
return cb(err);
}
fs.readFile('file1','utf-8',function(err, data){
if(err){
return cb(err);
}
cb(null,[content,data]);
}
);
});
下面那段代码再多来几个异步就成了“回调地狱”,而async模块给我们封装好了这段代码,我们只需要愉快的写成优雅的顺序模式就好了。
*在node的异步并发中有一个问题就是当并发量过大时,下层服务器会吃不消,因为node的I/O操作是调用子线程去实现的,并且并发量过大,操作系统的文件描述符数量会用光并抛出错误too many open files。在这里一个解决方案就是使用bagpipe模块,该模块可以对异步API进行过载保护。
ok,异步编程解决方案就介绍到这里,有兴趣的同学还可以去看看ES7的Async/Await模块,那也是一种很优雅的异步编程方式。