JS-異步函數鏈式調用(更新:20181221)

2018-12-21 更新
一、簡化調用方式,更貼近普通函數的風格;
精簡版戳這裏segmentfault

2018-12-05 更新
一、支持頭節點入參;
二、簡化調用方式;數組

//源碼
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...]
);

節點模板:this

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的功能:將參數打印出來;code

// 組合鏈式關係 ...
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),並傳出:ip

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:get

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()

鏈式調用回調函數都作了什麼?
一、依照入參中的前後順序,動態組合了回調函數;
二、將當前的【callback】和下一個對象的【fnCont】進行映射綁定;

使用過程應注意什麼?
一、根據實際狀況調整回調順序,或者增長方法;
二、每一節的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
相關文章
相關標籤/搜索