资讯专栏INFORMATION COLUMN

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

lidashuang / 2834人阅读

摘要:基数,倒计时进入倒计时进入倒计时进入倒计时进入倒计时进入倒计时倒计数结束执行完毕,结果为,准备进入。

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

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

//源码
function chainedFn(chain,firstFnArguments){
    // 入参数据校验 ...
    for(var i=0;i

链条模板:

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

链条模板:

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



文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/88571.html

相关文章

  • 浅析JavaScript异步

    摘要:回调函数,一般在同步情境下是最后执行的,而在异步情境下有可能不执行,因为事件没有被触发或者条件不满足。同步方式请求异步同步请求当请求开始发送时,浏览器事件线程通知主线程,让线程发送数据请求,主线程收到 一直以来都知道JavaScript是一门单线程语言,在笔试过程中不断的遇到一些输出结果的问题,考量的是对异步编程掌握情况。一般被问到异步的时候脑子里第一反应就是Ajax,setTimse...

    Tangpj 评论0 收藏0
  • JavaScript 工作原理之四-事件循环及异步编程的出现和 5 种更好的 async/await

    摘要:函数会在之后的某个时刻触发事件定时器。事件循环中的这样一次遍历被称为一个。执行完毕并出栈。当定时器过期,宿主环境会把回调函数添加至事件循环队列中,然后,在未来的某个取出并执行该事件。 原文请查阅这里,略有改动。 本系列持续更新中,Github 地址请查阅这里。 这是 JavaScript 工作原理的第四章。 现在,我们将会通过回顾单线程环境下编程的弊端及如何克服这些困难以创建令人惊叹...

    maochunguang 评论0 收藏0
  • 异步发展流程 —— 手写一个符合 Promise/A+ 规范的 Promise

    摘要:构造函数的实现我们在使用的时候其实是使用关键字创建了一个的实例,其实是一个类,即构造函数,下面来实现构造函数。 showImg(https://segmentfault.com/img/remote/1460000018998456); 阅读原文 概述 Promise 是 js 异步编程的一种解决方案,避免了 回调地狱 给编程带来的麻烦,在 ES6 中成为了标准,这篇文章重点不是叙...

    UnixAgain 评论0 收藏0
  • Promise学习总结

    摘要:引擎线程也称为内核,负责处理脚本程序例如引擎引擎线程负责解析脚本,运行代码。对象代表一个未完成但预计将来会完成的操作。注意一旦新建就会立即执行它属于,无法取消。 写在前面: 第一遍学Promise时, 只是大概过了一遍, 感觉学的不够深入, 这一篇算是对之前的一个总结吧. Promise在ES6中也属于一个较难理解的一部分; 所以在学习一个比较难理解的知识点时, 我们可以围绕这个知识点...

    twohappy 评论0 收藏0

发表评论

0条评论

lidashuang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<