本文系原创,转载请注明出处~
用Node做业务,当业务较为复杂的时候,遇到的一个困扰是回调层次变深。我相信尝试使用node的程序猿经过一段时间适应,自己的脑子在面对‘回调金字塔’的时候比其他人更加从容。但当逻辑太复杂或者需求改变需要改点儿地方的时候,‘回调金字塔’的代码结构,使得代码难以维护和阅读。
se6的Generator很好解决了这个问题,使得异步的回调写起来和线性的同步编程形式一致,而运行机理又完全是异步的。换句话说,好处你都得了,性能一点儿没损失。
这篇文章讲的是原理,不赘述了,下面直接贴出一段源码,
require("babel/polyfill"); // ES6 generator support in Babel
let Promise = require("bluebird")
var co = Promise.coroutine;
let rcib = require('rcib');
let postDelayBySecAsync = Promise.promisify(rcib.postDelayBySec, rcib);
let hco = co(function* () {
yield postDelayBySecAsync(5)
console.log('finish')
});
hco().then( function() {
}).catch(function(e){
console.error(e.message);
});
这是使用generator的一般形式,npm安装了这些依赖库之后,运行它,等待5s之后,打印出finish。如果使用node4.x.x的版本,运行前使用gulp工具编译一下,才能运行。
RCIB是我个人开发的一个使用C++扩展node能力的一般性框架,可以用来解决cpu密集型任务。与node使用js闭包实现异步的方法相比,rcib使用C++闭包扩展node。
被co包起来的部分,称为generator。异步函数,经过Promise.promisify 转化之后,皆可放入generator内部使用。注意,异步函数的参数需要是如下形式(p1,p2,...,function(err, data){})
以上代码如果写成一般形式,则为
rcib.postDelayBySec(5, function(err){
console.log('finish')
})
可见使用Generator,回调的写法被展平,异步代码在形式上如同步执行。以同步之形,行异步之实。一劳永逸的解决了代码形式上多重回调嵌套的弊端。
异步的并行执行 试想,如果前后阶段的执行彼此不依赖,那么使用generator,任务执行的时间为两者处理时间之和。而处理时间可以更快,因为不依赖,所以任务处理完成的最小时间是两者单独处理时间较大的那一个。async模块有一个方法,名字叫做parallel,能够并行异步操作,收集结果,执行完需要的时间是一系列异步操作中需要时间最长的那个。pyield 支持yield并发,例如:
let hco = co(function* () {
yield redis.set('a', 'hello a');
yield redis.set('b', 'hello b');
yield redis.set('c', 'hello c');
//console.log(yield redis.get('c'));
let allrel = yield parallelAsync([
{obj: redis, func: redis.get, param:['a']}
,{obj: redis, func: redis.get, param:['b']}
,{obj: redis, func: redis.get, param:['c']}]);
if(!allrel) console.error('fail');
else console.log(allrel);
});
通过这种方式,并行执行,收集完所有结果后,将结果打印出来。
总结,本文直接给出使用Generator的一般形式,无论从一致性,代码美感,还是易用性上看,generator都是一个一劳永逸的解决方案。
上一篇文章 Node.js高性能挖掘— 1异步编程 下一篇—node.js与V8引擎
这个系列还应该包括 Node.js 与 V8引擎 内存控制与Buffer 模块加载和执行 工具类第三方模块