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