async 作为大名鼎鼎的异步控制流程包,在npmjs.org 排名稳居前五,目前已经逐渐形成node.js下控制异步流程的一个规范.async成为异步编码流程控制的老大哥绝非偶然,它不仅使用方便,文档完善,把你杂乱无章的代码结构化,生辰嵌套的回掉清晰化.
async 提供的api包括三个部分:
(1)流程控制 常见的几种流程控制.
(2)集合处理 异步操作处理集合中的数据.
(3)工具类 .
github 开源地址: https://github.com/caolan/async
安装方法: npm install async
使用方法: var async=require('async');
串行且无关联
场景:提取某学校大三学生的学生信息(假设每个班级存储在一个独立的数据表里)
分析:每个班级的学生之间是无关联的,假设共有3个班级,我们需要遍历3个表,把提取出的学生信息返回客户端一个json,如下
{ "1班":[{name:"张三",age:"21",class:"1班"},......(省略N个学生)] "2班":[{name:"李四",age:"22",class:"2班"},......(省略N个学生)] "3班":[{name:"王五",age:"22",class:"3班"},......(省略N个学生)] }
如果不使用 async
var class=require('./module/class'); export.pageStudent=function(req,res){ var rtnJson={}; class.getStudent('1班',function(error,oneResult){ if(!error&&oneResult){ rtnJson['1班']=oneResult; class.getStudent('2班',function(error,twoResult){ if(!error&&twoResult){ rtnJson['2班']=twoResult; class.getStudent('3班',function(error,threeResult){ if(!error&&threeResult){ rtnJson['3班']=threeResult; //3个班级全部获取完成 res.render('./veiw/pageStudent',{students:rtnJson}); }else{ res.render('./veiw/pageStudent',{students:rtnJson}); } }); }else{ res.render('./veiw/pageStudent',{students:rtnJson}); } }); }else{ res.render('./veiw/pageStudent',{students:rtnJson}); } }); }
如果某个年级有8个班级,那这样嵌套下去会是什么样的结果.....如果有一天修改逻辑,自己回头查看自己的代码也是一头雾水,不知该从哪下手.
利用 async series 控制串行无关联流程,用法如下:
async.series({ flag1:function(done){ //flag1 是一个流程标识,用户自定义 //逻辑处理 done(null,返回结果)// 第一个参数是异常错误,第二个参数的返回结果 }, flag2:function(done){ //逻辑处理 done('error info',null) //如果返回错误信息, //下面的流程控制将会被中断,直接跳到最后结果函数 }, },function(error,result){ //最后结果 //result是返回结果总集,包含了所有的流程控制 , //result.flag1 可以获取标识1中处理的结果 });
所以我们用 series 来串行控制一下这个流程:
async.series({ oneClass:function(done){ class.getStudent('1班',function(error,oneResult){ if(!error) done(null,oneResult); else done(error,null); }); }, twoClass:function(done){ class.getStudent('2班',function(error,twoResult){ if(!error) done(null,twoResult); else done(error,null); } }, threeClass:function(done){ class.getStudent('3班',function(error,threeResult){ if(!error) done(null,threeResult); else done(error,null); } } },function(error,result){ if(!error) callback(null,result); else callback(error,null); });
上面是一个标准的串行流程,代码可读性很强, 容易维护,但是这种流程只适合按顺序执行且每一步没有关联
如果你的业务逻辑是根本不需要按顺序执行的,比如获取不同班级的信息,其实先获取1班和先获取3班是一样的,只要最后结果保证3个班的人员信息都获取成功即可.所以这里用series 是一种错误,反而和 node.js 的异步IO相互矛盾.应该用 并行且无关联的控制流程.
串行无关联模式要求每一步执行成功后才能执行下一步流程.所以是一个同步编程思想.看我写的一个demo 的执行时间
//测试代码,没有任何逻辑处理,按4步执行,最后看执行时间. console.time('series'); var async = require('async'); async.series({ one: function (done) { //处理逻辑 done(null, 'one'); }, two: function (done) { //处理逻辑 done(null, 'tow'); }, three: function (done) { //处理逻辑 done(null, 'three'); }, four: function (done) { //处理逻辑 done(null, 'four'); } }, function (error, result) { console.log('one:', result.one); console.log('two:', result.two); console.log('three:', result.three); console.log('four:', result.four); console.timeEnd('series'); })
分4步串行控制,最后耗时 14毫秒
并行且无关联
场景如上,获取4个班级学生信息.
async 里的提供的并行无关联 api 是 parallel
parallel 的原理是同时并行处理每一个流程,最后汇总结果,如果某一个流程出错就退出.把获取班级成员信息的代码用 parallel 来实现如下
async.parallel({ oneClass:function(done){ class.getStudent('1班',function(error,oneResult){ if(!error) done(null,oneResult); else done(error,null); }); }, twoClass:function(done){ class.getStudent('2班',function(error,twoResult){ if(!error) done(null,twoResult); else done(error,null); } }, threeClass:function(done){ class.getStudent('3班',function(error,threeResult){ if(!error) done(null,threeResult); else done(error,null); } } },function(error,result){ if(!error) callback(null,result); else callback(error,null); });
看上去和串行无关联的代码只是换了一个关键词而已,确实是这样,他们接收的参数形式完全一致.但是实现方法却完全不同,还是用demo数据来测试
var async = require('async'); console.time('parallel'); async.parallel({ one: function (done) { //处理逻辑 done(null, 'one'); }, two: function (done) { //处理逻辑 done(null, 'tow'); }, three: function (done) { //处理逻辑 done(null, 'three'); }, four: function (done) { //处理逻辑 done(null, 'four'); } }, function (error, result) { console.log('one:', result.one); console.log('two:', result.two); console.log('three:', result.three); console.log('four:', result.four); console.timeEnd('parallel'); })
一样是4个控制流程,并行模式下耗时 3毫秒,大约接近串行模式耗时1/5 (此比例是单样本统计,只供参考)
串行且有关联
场景:打开微博首页需要加载 微博个人信息,微博分组信息,微博分组粉丝信息 这里不考虑ajax 异步拉取每个模块.如果我们用ejs来渲染,需要发送给前端页面一个这样的数据结构(简单的模拟数据)
{ userInfo:{userID:10001,totalNum:368,fans:562,follow:369} group:[{groupID:100,groupName:"粉丝"},{groupID:200,groupName:"同事"}...], fansGroup:{"粉丝":[{nickName:'aa',age:20},{nickName:'bb',age:22}....]} }
上面的信息取自3个不同的表,但是每一个流程都和上一个流程有关系,也就是说,如果拿到用户信息后,根据用户ID 获取此微博用户的分组,根据分组ID获取每个组里面的粉丝.一环扣一环,希望流程按顺序执行,且每一步逻辑控制都能由上一步得到的结果来做条件.
var userInfo=require('./lib/module/userInfo'); var group=require('./lib/module/group'); var groupFans=require('./lib/module/groupFans'); //传统嵌套代码如下 export.pageIndex=function(req,res){ userInfo.get(userEmail,passWord,function(error,userInfo){ group.get(userInfo.userID,function(error,groupList){ var idx=0,fansList=[]; for(var i=0;i<groupList.length;i++){ groupFans.get(groupList[idx++],function(error,fansInfo){ fansList.push(fansInfo); if(idx==groupList.length){ callback(null,{userInfo:userInfo,group:groupList,fansGroup:fansList}); } }) } }); }); }
上面的代码互相牵扯关系,每一步的逻辑运算都需要上一步的结果来支持,我们假设每一步都运行正确,没有对error 进行判断.
最后因为要遍历数组中元素,然后把每个元素对应的分组成员都组合起来,我们用到了数据索引计数器 idx,上面的代码看似没有问题,但是索引计数器非常不好控制,稍有差错可能会不运行.
anync 的waterfall 适合上面的场景.
waterfall 每一步执行时需要由上一步执行的结果当做参数.所以每一步必须串行等待.事例代码如下:
console.time('waterfall'); async.waterfall([ function (done) { done(null, 'one'); }, function (onearg, done) { done(null, onearg + '| two'); }, function (twoarg, done) { done(null, twoarg + '| three'); }, function (threearg, done) { done(null, threearg + '| four'); } ], function (error, result) { console.log(result); console.timeEnd('waterfall'); })
上面调用 waterfall 函数时传入一个数组,数组总的每一个元素就是一个串行控制节点,每一个节点执行必须保证上一节点已经执行完成且拿到结果.这样将结果传入下一个控制节点作为参数来运行.
参数数组第一个元素回调函数 done(null,'one') -->null 说明执行没有错误, 'one' 是第一个节点运行返回的结果(这个结果将会传入第二个控制流程来作为参数).....这样以此类推,最后一个元素(第四个)返回的结果应该是 'one|tow|three|four ' 这个字符串,也就是 result 打印出的内容.
注意:
async 提供的api默认支持多种传递参数的写法,我个人比较喜欢用对象表示法来传递( json格式) 但是waterfall 这个api很特殊,不支持对象参数,如果你用下面的错误代码来调用 waterfall 的话,你不会拿到运行结果.
//此调用方法是错误的!!! console.time('waterfall'); async.waterfall({ one: function (done) { //处理逻辑 done(null, 'one'); }, two: function (onearg, done) { //处理逻辑 console.log('-----', onearg); done(null, onearg + 'two'); }, three: function (twoarg, done) { //处理逻辑 done(null, twoarg + 'three'); }, four: function (threearg, done) { //处理逻辑 done(null, threearg + 'four'); } }, function (error, result) { console.log(result); console.timeEnd('waterfall'); })
最后由于拿不到返回结果,调用结果为 undefined
智能控制 auto
如果你的逻辑代码很繁琐,涉及到很多的流程控制,但是部分流程是相互依赖的,部分又是无依赖关系而并行独立的.这时 auto 这个智能控制流程 api 再适合你不过了. 后续补充....
本文由 一介布衣 创作,采用 知识共享署名 3.0 中国大陆许可协议。
可自由转载、引用,但需署名作者且注明文章出处。
本站部署于「UCloud云计算」。
右侧微信扫描 「打赏」,感谢支持!