JS-异步函数链式调用(更新:20181221)

2018-12-21 更新
1、简化调用方式,更贴近普通函数的风格;
精简版戳这里

2018-12-05 更新
1、支持头节点入参;
2、简化调用方式;

//源码
function chainedFn(chain,firstFnArguments){
    // 入参数据校验 ...
    for(var i=0;i<chain.length;i++){
        if(!chain[i]["fnName"] || (typeof chain[i]["fnName"]) != "function"){
            console.log("【error】链条参数有误!");
            return;
        }
    }

    // 组合链条 ...
    var firstFn = (function combinationFn(index){
        var curFnIndex = index || 0; //当前函数索引
        var curArg = chain[curFnIndex]["fnArg"] || ""; //当前函数参数
        var callBack = ""; //当前函数参数回调

        // 如果存在下一条,则将下一条绑定为当前的回调 ...
        if(curFnIndex + 1 <  chain.length){
            callBack = arguments.callee(curFnIndex + 1).fnCont;
        }

        var curFn = new chain[curFnIndex]["fnName"](callBack,curArg);
        if(curFn){
            return curFn;
        }else{
            return false;
        }

    })();

    // 启动链条 ...
    if(typeof firstFn.fnCont == "function"){
        var suctnParam = "";
        for(var i = 0 ; i < firstFnArguments.length; i ++)
        {
            suctnParam += "firstFnArguments[" + i + "]" + (i == firstFnArguments.length - 1 ? "" : ",");
        }
        eval("firstFn.fnCont(" + suctnParam + ")");

    }
}
    

链条模板:

chainedFn([
        {"fnName":方法名,"fnArg":实例化时的入参对象(非必传)},
        {"fnName":方法名,"fnArg":实例化时的入参对象(非必传)},
        {"fnName":方法名,"fnArg":实例化时的入参对象(非必传)}
    ],[头节点入参1,头节点入参2,头节点入参3...]
);

节点模板:

function 函数名(callback,链条上的【fnArg】(可选)){ //callback
    this.fnCont = function(...){ //如果是头节点,则入参对应chainedFn的第二个参数;否则等价于上一节点的【callback】
        //TODO...
        if(typeof callback == "function"){
            callback(...); // 等价于下一个节点的【fuCont】
        }
    }
}

函数示例:
假设现在有3个需要同步执行的函数:FnA,FnB,FnC;
FnA的功能:将基数(入参1),乘上乘积(入参2),结果值和倒计时(入参3)传给FnB;
FnB的功能:进入倒计时,倒计时结束后,将入参乘上5,然后传给fnC;
FnC的功能:将参数打印出来;

// 组合链式关系 ...
chainedFn([
    {"fnName":FnA}, //规定初始值
    {"fnName":FnB,"fnArg":"test"},//倒计时结束后,进行一些操作
    {"fnName":FnC}//展示处理结果
    ],[2,10,5]
);

// FnA的功能:将入参1乘上入参2,然后执行FnB,同时设置fnB的倒计时时间(入参3)...
function FnA(callback){
    this.fnCont = function(base,multiplier,cDown){
        console.log("【from FnA】基数:" + base + ",乘积:" + multiplier + ",倒计时:" + cDown);
        var num = base * multiplier ;
        if(typeof callback == "function"){
            console.log("【from FnA】执行完毕,结果为:" + num + ",准备进入FnB。");
            callback(num,cDown); // 等价于【FnB】的fuCont
        }
    }
}

// FnB的功能:倒计时结束后,将入参乘上5 ...
function FnB(callback,fnArg){
    this.fnCont = function(base,cDown){
        alert(fnArg);
        console.log("【from FnB】基数:" + base + ",倒计时:" + cDown);
        var countDown = cDown;
        var tTout = setInterval(function(){
            console.log("【from FnB】进入倒计时 -> " + --countDown + "s");
            if(countDown <= 0){
                console.log("【from FnB】倒计数结束");
                countDown = -1;
                clearTimeout(tTout);

                var num = base * 5;

                if(typeof callback == "function"){
                    console.log("【from FnB】执行完毕,结果为:" + num + ",准备进入FnC。");
                    callback(num);// 等价于【FnC】的fuCont
                }
            }
        },1000);

    }
};

// 将参数打印出来 ...
function FnC(callback){
    this.fnCont = function(tArg){
        console.log("【from FnC】计算结果为:" + tArg);
        if(typeof callback == "function"){
            callback();
        }
    }
};

执行结果:

【from FnA】基数:2,乘积:10,倒计时:5
【from FnA】执行完毕,结果为:20,准备进入FnB。
【from FnB】基数:20,倒计时:5
【from FnB】进入倒计时 -> 4s
【from FnB】进入倒计时 -> 3s
【from FnB】进入倒计时 -> 2s
【from FnB】进入倒计时 -> 1s
【from FnB】进入倒计时 -> 0s
【from FnB】倒计数结束
【from FnB】执行完毕,结果为:100,准备进入FnC。
【from FnC】计算结果为:100

此时突然新增一个需求:在FnA中指定一个值B,内容FnC输出值A不可大于B:如果A超过B则取B,否则取A:

FnA增加指定参数(maxNum),并传出:

function FnA(callback){
    this.fnCont = function(base,multiplier,cDown,maxNum){
        console.log("【from FnA】基数:" + base + ",乘积:" + multiplier + ",倒计时:" + cDown);
        var num = base * multiplier ;
        if(typeof callback == "function"){
            console.log("【from FnA】执行完毕,结果为:" + num + ",准备进入下一节点");
            callback(num,cDown,maxNum); // 等价于【FnB】的fuCont
        }
    }
}

FnB原封不动将值传出:

function FnB(callback){
    this.fnCont = function(base,cDown,maxNum){
        console.log("【from FnB】基数:" + base + ",倒计时:" + cDown);
        var countDown = cDown;
        var tTout = setInterval(function(){
            console.log("from FnB:进入倒计时 -> " + --countDown + "s");
            if(countDown <= 0){
                console.log("【from FnB】倒计数结束");
                countDown = -1;
                clearTimeout(tTout);

                var num = base * 5;

                if(typeof callback == "function"){
                    console.log("【from FnB】执行完毕,结果为:" + num + ",准备进入下一节点");
                    callback(num,maxNum);// 等价于【FnC】的fuCont
                }
            }
        },1000);

    }
};

新增一个方法(FnD),此函接受两个入参A,B。如果A小于B,则返回A,否则返回B:

function FnD(callback){
    this.fnCont = function(num,max){
        var tmpNum = num;
        var maxNum = max;

        if(tmpNum > maxNum){
            tmpNum = maxNum;
            console.log("【from FnD】计算结果为:" + tArg);
        }
        if(typeof callback == "function"){
            callback(tmpNum);
        }
    }
};

调整链式结构(指定最大值,增加FnD):

// 组合链式关系 ...
chainedFn([
    {"fnName":FnA},//规定初始值
    {"fnName":FnB},//倒计时结束后,进行一些操作
    {"fnName":FnD},//最大值限制
    {"fnName":FnC} //展示处理结果
    ],[2,10,3,50]
);

输出结果:

【from FnA】基数:2,乘积:10,倒计时:3
【from FnA】执行完毕,结果为:20,准备进入下一节点
【from FnB】基数:20,倒计时:3
【from FnB】进入倒计时 -> 2s
【from FnB】进入倒计时 -> 1s
【from FnB】进入倒计时 -> 0s
【from FnB】倒计数结束
【from FnB】执行完毕,结果为:100,准备进入下一节点
【from FnD】值(100)超出限制,取限制值:50
【from FnC】计算结果为:50


聊一下背景
最近在开发项目的时候,因为需要数据同步处理,所以会遇到很多涉及到函数回调的地方。最多的达到了7个!不管是自己撸代码,还是维护代码,基本都是一项很烦心的事,特别要改原先其他同事写的逻辑,我宁愿重新写。

代码都是这样的:

a(xx,function(){
    b(xx,function(){
        c(xx,function(){
        .....
        });
    });
});

又或者这样的:

    function a(xx){
    //.....
    b();
    }
    function b(xx){
    //.....
    c();
    }
    
    ......

故,抽了点时间写了一个:以链式调用的形式用来处理需要回调的函数。

源码:

/**
*链式回调
**/
function chainedFn(chain){
    // 入参数据校验 ...
    for(var i=0;i<chain.length;i++){
        if(!chain[i]["fnName"] || (typeof chain[i]["fnName"]) != "function"){
            console.log("error:参数有误!");
            return;
        }
    }
    
    // 如果只有一条,则直接调用 ...
    if(chain.length < 2){
        (new chain[0]["fnName"](chain[0]["fnArg"]||"")).fnCont();
        return;
    }
    
    // 组合链条 ...
    var firstFn = (function combinationFn(index){
        var curFnIndex = index || 0; //当前函数索引
        var curArg = chain[curFnIndex]["fnArg"] || ""; //当前函数参数
        var callBack = ""; //当前函数参数回调
        
        // 如果存在下一条,则将下一条绑定为当前的回调 ...
        if(curFnIndex + 1 <  chain.length){
            callBack  = combinationFn(curFnIndex + 1).fnCont;
        }
        
        var curFn = new chain[curFnIndex]["fnName"](curArg,callBack);    
        if(curFn){
            return curFn;
        }else{
            return false;
        }
        
    })();
    
    // 启动链条 ...
    if(typeof firstFn.fnCont == "function"){
        firstFn.fnCont();
    }
}


链条模板:

chainedFn([
    {"fnName":方法名A,"fnArg":实例化时的入参对象(非必传)},
    {"fnName":方法名B,"fnArg":实例化时的入参对象(非必传)},
    {"fnName":方法名C,"fnArg":实例化时的入参对象(非必传)}
]);
   
说明:chainedFn的入参是JSON数组,【方法名名】需存在,且符合“对象模版”;【实例化时的入参】参数可无;
   

方法模版:

function **方法名**(**实例化参数**,callback){
    this.fnCont = function(tArg1,tArg2...){ //fnCont:函数主体(函数名不可变),tArg:被回调的入参(链条上一节的入参)
        // 函数功能代码 ...
        
        
        if(typeof callback == "function"){
            callback(tArg1,tArg2...);//回调的入参(链条下一节的入参)
        }
    }
};
说明:【实例化参数】可为空;【fnCont】为函数主体,函数名不可修改;当前一节【fnCont】等于上一节的【callback】即: 
   A.callback() === B.fnCont()
   B.callback() === C.fnCont()


链式调用回调函数都做了什么?
1、依照入参中的先后顺序,动态组合了回调函数;
2、将当前的【callback】和下一个对象的【fnCont】进行映射绑定;

使用过程应注意什么?
1、根据实际情况调整回调顺序,或者增加方法;
2、每一节的callback即上下一节的fnCont;

函数示例:
假设现在有3个需要同步执行的函数:FnA,FnB,FnC;
FnA的功能:将入参乘上10,然后传给fnB,同时规定fnB的倒计时时间;
FnB的功能:进入倒计时,倒计时结束后,将入参乘上5,然后传给fnC;
FnC的功能:将参数打印出来;

// FnA的功能:将入参乘上10,然后执行FnB,同时设置fnB的倒计时时间(10s)...
function FnA(arg,callback){
    this.fnCont = function(){
        var num = arg * 10 ;
        if(typeof callback == "function"){
            callback(num,10); // 等价于【FnB】的fuCont
        }
    }
}

// FnB的功能:倒计时结束后,将入参乘上5,然后执行FnC ...
function FnB(arg,callback){
    this.fnCont = function(tArg,cDown){
        var num = tArg * 5;
        var countDown = cDown;
        var tTout = setInterval(function(){
            console.log("from FnB:进入倒计时 -> " + --countDown + "s");
            if(countDown <= 0){
                console.log("from FnB:倒计数结束");
                countDown = -1;
                clearTimeout(tTout);

                if(typeof callback == "function"){
                    console.log("from FnB:执行回调函数");
                    callback(num);// 等价于【FnC】的fuCont
                }
            }
        },1000);

    }
};

// 将参数打印出来 ...
function FnC(arg,callback){
    this.fnCont = function(tArg,awr){
        console.log("from FnC:入参的值是" + tArg);
        if(typeof callback == "function"){
            callback();
        }
    }
};


// 组合链式关系 ...
chainedFn([
       {"fnName":FnA,"fnArg":2}, //规定初始值
       {"fnName":FnB},//倒计时结束后,进行一些操作
       {"fnName":FnC},//展示处理结果
       ]);
   

执行结果:

from FnA:入参的值是 2
userCenter.js?v=undefined:63 from FnB:进入倒计时 -> 10s
userCenter.js?v=undefined:65 from FnB:进入倒计时 -> 9s
userCenter.js?v=undefined:65 from FnB:进入倒计时 -> 8s
userCenter.js?v=undefined:65 from FnB:进入倒计时 -> 7s
userCenter.js?v=undefined:65 from FnB:进入倒计时 -> 6s
userCenter.js?v=undefined:65 from FnB:进入倒计时 -> 5s
userCenter.js?v=undefined:65 from FnB:进入倒计时 -> 4s
userCenter.js?v=undefined:65 from FnB:进入倒计时 -> 3s
userCenter.js?v=undefined:65 from FnB:进入倒计时 -> 2s
userCenter.js?v=undefined:65 from FnB:进入倒计时 -> 1s
userCenter.js?v=undefined:65 from FnB:进入倒计时 -> 0s
userCenter.js?v=undefined:67 from FnB:倒计数结束
userCenter.js?v=undefined:82 from FnC:入参的值是100



    原文作者:Lycop
    原文地址: https://segmentfault.com/a/1190000011297044
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞