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