控制异步回调利器 - async 串行series,并行parallel,智能控制auto简介

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云计算」。
右侧微信扫描 「打赏」,感谢支持!

时间: 2024-10-03 21:41:22

控制异步回调利器 - async 串行series,并行parallel,智能控制auto简介的相关文章

Node.js 使用回调函数实现串行流程控制

下面是一个使用Node.js回调函数实现串行流程控制的示例: setTimeout(function() { console.log('I excute first.'); setTimeout(function() { console.log('I excute next.'); setTimeout(function() { console.log('I excute last.'); }, 100); }, 500); }, 1000);

【iOS面试系列-2】多线程中同步、异步和串行、并行之间的逻辑关系(必考,必须掌握)

一.同步.异步和串行.并行 任务串行执行就是每次只有一个任务被执行,任务并发执行就是在同一时间可以有多个任务被执行. 一个同步函数只在完成了它预定的任务后才返回.一个异步函数,刚好相反,会立即返回,预定的任务会完成但不会等它完成.因此,一个异步函数不会阻塞当前线程去执行下一个函数. (来源:http://www.cocoachina.com/industry/20140428/8248.html) 队列分为串行和并行 任务的执行分为同步和异步 -------  队列只是负责任务的调度,而不负责任

iOS-线程之GCD方式---之同步异步和串行队列并行队列之间的关系

GCD方式是Apple官方推荐实现多线程的方式 但在这之前必须理清楚同步,异步,串行队列,并行队列之间的概念. 同步:即当前的执行程序块不会创建一个新的线程,只能在当前线程中执行. 异步:会在当前的线程之外创建一个新的线程,并在新的线程中执行代码块. 首先声明一点,队列是用来存放即将执行的线程体的. 串行队列:串行队列中的线程满足FIFO(First In First Out),并且只有在先出的线程执行完,后续的线程才能出队列执行.(很可能造成APP的假死状态) 并行队列:并行队列也满足FIFO

ios--进程/多线程/同步任务/异步任务/串行队列/并行队列(对比分析)

现在先说两个基本的概念,啥是进程,啥是线程,啥又是多线程;先把这两个总是给弄清再讲下面的 进程:正在进行的程序,我们就叫它进程. 线程:线程就是进程中的一个独立的执行路径.这句话怎么理解呢! 一个程序它是按顺序从上往下执行的, 这个执行顺序我们可以把它看成是一条线,把这条线就叫做线程(个人理解,错了勿喷);每一个程序中至少包含一条线程, 这条线程,我们叫它主线程. 多线程:多线程也就是说一个程序中有多条执行路径.在iOS当中将一些比较耗时的操作放到另一条执行路径里.让它与主线程同时运行.这样不会

async/await实现图片的串行、并行加载

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" con

linux总结应用之三 安装和配置串行,并行链路

 (一)远程站的设置: 最简单的做法是在远程的机器专为拨号连接建立PPP 登陆项 : ppp:off:700:700:ppp acount:/home/ppp:home/ppp/ppplogin 为账号建立起始目录; # mkdir   /home/ppp #  chown  ppp. /home/ppp 注意在新加的行中,将下列程序作为登陆的shell: /home/ppp/ppplogin 实际上,它不是shell程序.而是在远程机上用来启动pppd守护程序的script. 它的典型形式如下

串行与并行

1.数据传送方式不同:串口传输方式为数据排成一行.一位一位送出接收也一样,并口传输8位数据一次送出.2.针脚不同:串口针脚少,并口针脚多.3.用途不同:串口现在只用作控制接口,并口多用作打印机.扫描仪等接口. 什么是并口和串口?有什么区别?并行与串行? 串口一般指硬件上的COM接口.一般的PC主板都提供两个串口 而并口.一般指指打印接口. 通常并口是两排除23针.而对应的串口是两排九针.当然在老式的机子也有串口是23针的.但很少了.因为随着计算机的发展,这种老式的板几乎被淘汰了.还有VGA接口(

R语言串行与并行Apply用法

串行 APPLY<- function(m){ mTemp <- apply(m, 2, mysort) return(mTemp)} snowfall包的并行 SNOWFALL<-function(m,ncl){ library(snowfall) sfInit(parallel = TRUE, cpus = ncl) mTemp<- sfApply(m,2,mysort) sfStop() return(mTemp)}

JVM_垃圾回收串行、并行、并发算法(总结)

一.串行 JDK1.5前的默认算法 缺点是只有一个线程,执行垃圾回收时程序停止的时间比较长 语法 -XX:+UseSerialGC 新生代.老年代使用串行回收 新生代复制算法 老年代标记-压缩 示例图 测试代码 //-Xmx20m -Xms20m -Xmn2m -XX:+UseSerialGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps byte[] b = null; for (int i = 0; i < 7; i++) { b = new byte