写Node.js的时候处理稍微复杂点的逻辑经常出现大量的回调函数,导致代码几乎不可维护,例如:
var add= function (v1, v2, v3){
console.log(v1+v2+v3+'');
};
var value1,value2,value3
clinet.get("key1", function (err, data) {
// do something
value1 = data
clinet.get("key2", function (err, data) {
// do something
value2=data
clinet.get("key3", function (err, data) {
//do something
value3 = data
add(value1, value2, value3);
});
});
});
如果业务逻辑更加复杂,那函数的嵌套更加复杂,可读性和可维护性那就可想而知了。
Node.js回调黑洞让很多人对它望而却步,但是上官网看到一个排名第二的库async,看见名字就大概知道是同步库,用了一下,吓了一跳,一个34K的库竟然如此强大,1300多行代码竟然能扭转乾坤。
对作者的佩服的五体投地。
async主要实现了三个部分的流程控制功能:
- 集合: Collections
- 流程控制: Control Flow
- 工具类: Utils
1). 集合: Collections
- each: 如果想对同一个集合中的所有元素都执行同一个异步操作。
- map: 对集合中的每一个元素,执行某个异步操作,得到结果。所有的结果将汇总到最终的callback里。与each的区别是,each只关心操作不管最后的值,而map关心的最后产生的值。
- filter: 使用异步操作对集合中的元素进行筛选, 需要注意的是,iterator的callback只有一个参数,只能接收true或false。
- reject: reject跟filter正好相反,当测试为true时则抛弃
- reduce: 可以让我们给定一个初始值,用它与集合中的每一个元素做运算,最后得到一个值。reduce从左向右来遍历元素,如果想从右向左,可使用reduceRight。
- detect: 用于取得集合中满足条件的第一个元素。
- sortBy: 对集合内的元素进行排序,依据每个元素进行某异步操作后产生的值,从小到大排序。
- some: 当集合中是否有至少一个元素满足条件时,最终callback得到的值为true,否则为false.
- every: 如果集合里每一个元素都满足条件,则传给最终回调的result为true,否则为false
- concat: 将多个异步操作的结果合并为一个数组。
2). 流程控制: Control Flow
- series: 串行执行,一个函数数组中的每个函数,每一个函数执行完成之后才能执行下一个函数。
- parallel: 并行执行多个函数,每个函数都是立即执行,不需要等待其它函数先执行。传给最终callback的数组中的数据按照tasks中声明的顺序,而不是执行完成的顺序。
- whilst: 相当于while,但其中的异步调用将在完成后才会进行下一次循环。
- doWhilst: 相当于do…while, doWhilst交换了fn,test的参数位置,先执行一次循环,再做test判断。
- until: until与whilst正好相反,当test为false时循环,与true时跳出。其它特性一致。
- doUntil: doUntil与doWhilst正好相反,当test为false时循环,与true时跳出。其它特性一致。
- forever: 无论条件循环执行,如果不出错,callback永远不被执行。
- waterfall: 按顺序依次执行一组函数。每个函数产生的值,都将传给下一个。
- compose: 创建一个包括一组异步函数的函数集合,每个函数会消费上一次函数的返回值。把f(),g(),h()异步函数,组合成f(g(h()))的形式,通过callback得到返回值。
- applyEach: 实现给一数组中每个函数传相同参数,通过callback返回。如果只传第一个参数,将返回一个函数对象,我可以传参调用。
- queue: 是一个串行的消息队列,通过限制了worker数量,不再一次性全部执行。当worker数量不够用时,新加入的任务将会排队等候,直到有新的worker可用。
- cargo: 一个串行的消息队列,类似于queue,通过限制了worker数量,不再一次性全部执行。不同之处在于,cargo每次会加载满额的任务做为任务单元,只有任务单元中全部执行完成后,才会加载新的任务单元。
- auto: 用来处理有依赖关系的多个任务的执行。
- iterator: 将一组函数包装成为一个iterator,初次调用此iterator时,会执行定义中的第一个函数并返回第二个函数以供调用。
- apply: 可以让我们给一个函数预绑定多个参数并生成一个可直接调用的新函数,简化代码。
- nextTick: 与nodejs的nextTick一样,再最后调用函数。
- times: 异步运行,times可以指定调用几次,并把结果合并到数组中返回
- timesSeries: 与time类似,唯一不同的是同步执行
3). 工具类: Utils
- memoize: 让某一个函数在内存中缓存它的计算结果。对于相同的参数,只计算一次,下次就直接拿到之前算好的结果。
- unmemoize: 让已经被缓存的函数,返回不缓存的函数引用。
- log: 执行某异步函数,并记录它的返回值,日志输出。
- dir: 与log类似,不同之处在于,会调用浏览器的console.dir()函数,显示为DOM视图。
- noConflict: 如果之前已经在全局域中定义了async变量,当导入本async.js时,会先把之前的async变量保存起来,然后覆盖它。仅仅用于浏览器端,在nodejs中没用,这里无法演示。
由于资料比较少,就自己整理了下。
waterfall 用法实例
看名字,瀑布的意思,解决函数回调嵌套,并且上一个函数的处理结果传递给下一个函数。
function queryStep1(table, cb) {
var sql = 'select * from ' + connection.escapeId(table) + 'limit 1'; //数据库查询
connection.query(sql, function (err, result) {
console.log('step1 ' + result);
cb(null, result); //回调第一个函数
})
}
function step2 (arge, cb) {
var a = JSON.stringify(arge);
console.log('step2 ' + a);
cb(null, '1'); //回调结果函数
}
async.waterfall([
function (cb) { cb(null, 'stu'); }, //类似于第一个函数的初始化
function (r, cb) { queryStep1(r, cb); },
function (arge, cb) { step2(arge, cb); }
], function (err, result) {
console.log(err); // null
console.log('result ' + result); // result 1 , 应为是串行,所以结果只有一个
});
series
series和waterfall非常像,但是不同是waterfall需要把上一个函数的结果传递给下一个函数,串行执行,series也是串行执行,不同是他不需要上一个函数的结果,也就相对独立,最后返回函数的result是一个数组,包括每个函数传入的参数。
function step1 (v, cb) {
console.log('step1 ' + v);
result+=1;
cb(null, result);
}
function step2 (v, cb) {
console.log('step2 ' + v);
v+=1;
cb(null, result);
}
function step3 (v, cb) {
console.log('step3 ' + v);
result+=1;
cb(null, result);
}
async.series({
a: function (cb){ step1('11', cb); },
b: function (cb){ step2('22', cb); },
c: function (cb){ step3('33', cb); }
},function (err, values){
console.log(err); // null
console.log(values); // 11 22 33
})
reduce 给定一个集合和一个值,集合从左到右依次与给定值运算。
var arr = [1, 2, 3];
function r(v, a, cb) {
console.log('r' + eval(v + a));
cb(null, eval(v+a)); //最后一次递归才执行,调用返回结果的匿名函数
}
async.reduce(arr, 10, function (v, a, cb) {
console.log('val:' + v + '||' + 'arr:' + a);
r(v, a, cb); //递归调用
}, function (err, result) {
//console.log(err);
console.log(result);
});