promis模式的javascript实现

    javascript语言是一门函数式编程的语言,回调函数的使用是常用的,特别是在异步编程时,回调函数会在异步函数完成之后调用。 但是在编程中会遇到这样一类问题,A任务完成之后 才可以开始B任务,B任务完成之后才可以执行C任务,C任务完成后,才能执行D任务。假设ABCD四个任务都是同步的,则应该有下面代码:

    task("A").done();
    task("B").done();
    task("C").done();
    task("D").done();

    假设ABCD四个任务不全是同步的,最极端的情况,四个都是异步的,则应该有下面回调嵌套式的代码:

    task("A").done(function(){
        task("B").done(function(){
            task("C").done(function(){
                task("D").done();
            });
        });
    });

    但是这样的有个比较明显的问题,假设不是四个任务,而是10个8个,按照上面的写法,肯定会横向拉得很长。再加上可能会有其他逻辑添加到回调中去,对整个代码的维护是极为不利的。

    那么有什么比较好的方式,可以解决这个问题。其实对于函数式编程语言,很早就有一种名为promis的设计模式,就是专门用来解决回调函数嵌套问题的。如果使用promis模式,上述嵌套代码可以写成如下链式结构:

    Deffered(task("A"))
            .then(task("B"))
            .then(task("C"))
            .then(task("D"));

    这样即便有再多的任务,都可以竖着排下去,逻辑上也可以解套,代码上也简单明了,比之上面的嵌套模式,要好太多。

    下面用一个比较活泼的例子,来实现以下promis模式。

    有这样一个问题:

    程序员小王想娶女神小米。

    小米说必须先让她爸爸认可,然后让妈妈认可,然后让大伯认可,且让妈妈认可的前提是爸爸认可,让大伯认可的前提是爸爸妈妈都认可,然后我在考虑~

    假设:他们各个人同意的可能性都为80%,而且都必须有一段时间考虑,每个人可以请求两次。

    问:小王能不能取到小米~?

    使用程序实现以上问题。

    按照面向对象的思维,分析以上问题,小王对每个人得请求方式都是一样的,所以可以把这个过程封装到一个Request类中。

    这个类至少有以下方法:

    aks:        问是否可以娶小米

    answer: 答 可以 或者 不可以

    fail :        如果请求失败了,就执行此方法

    done:    如果最终请求成功了,就执行此方法

    begin:      开始执行请求方法

    下面是我对这个类的实现:

    (function(win){
    
        function Request(name){
            if(!(this instanceof Request)){
                return new Request(name);
            }else {
                var p = Promis.create(this);
                this.name = name;
                this.promis = p;
            }
        }
    
        Request.prototype={
            promis:null,
            name:””,
            _isfirst:true,
            begin:function(){
                this.ask();
                this.answer();
            },
            fail:function(){
                alert(“求婚失败”);
            },
            done:function(){
                alert(“求婚成功”);
            },
            ask:function(){
                var name = this.name;
                var askany = ‘小王:’+name+’,我可以和小米在一起么?’;
                this._say(askany);
            },
            answer:function(){
                var name = this.name;
                var answerAny = name+”:我得想一下。。。”;
                this._say(answerAny);
                _this = this;
                setTimeout(function(){
                    if(_this._isAgree()){
                        _this._say(name+’:好吧我同意了~’);
                        _this.promis.resolve();
                    }else{
                        _this._say(name+’:我不同意’);
    
                        if(!_this._isfirst){
                            _this.promis.reject();
                            return;
                        }
                        _this._isfirst=false;
                        _this.begin();
                    }
                },1000);
            },
            _isAgree:function(){
                var randomNum = parseInt(Math.random()*5);
                if(randomNum>=3){
                    return false;
                }
                return true;
            },
            _say:function(any){
                $(“#logFrame”).append(‘<p>’+any+’</p>’);
            }
        }
    
        win.Request = Request;
    
    })(window);

    此时对单个人得请求就可以描述为以下方式:

    Request("爸爸").begin();

    可以发现在Request类中,有一个Promis类,这个Promis主要的作用是用来承接多个Request之间的状态流转。

    promis类至少应该包含如下方法:

    then:设置下一个任务

    resolve:当前任务完成后,激活下一个任务

    reject  :当前任务失败后,调用当前任务的fail方法

    我对Promis的封装如下:

    (function(win){
        function Promis(){
    
        }
        Promis.prototype = {
            task:null,
            nextTask:null,
            init:function(task){
                this.task = task;
            },
            then:function(task){
                this.nextTask = task;
                return task.promis;
            },
            resolve:function(){
                if(this.nextTask)
                    this.nextTask.begin();
                else
                    this.task.done();
            },
            reject:function(){
                this.task.fail();
            }
        }
    
        win.Promis = Promis;
        Promis.create=function(task){
            var p = new Promis();
            p.init(task);
            return p;
        }
    
        win.Deferred = function(task){
            task.begin();
            return task.promis;
        }
    
    })(window);

    如上,代码中有一个Deferred方法,用来激活首个任务。将Request和Promis两者组合在一起后,就可以使用链式写法完成以上需求,代码如下:

    $(document).ready(function () {
    
        $(“#beginBtn”).on(“click”,function(){
            $(“#logFrame”).html(“”)
                          .append(‘<p>程序员小王想娶女神小米。</p>’);
            Deferred(Request(“爸爸”))
                .then(Request(“妈妈”))
                .then(Request(“大伯”))
                .then(Request(“小米”));
        });
    });

    最后附上html代码:

    <!DOCTYPE html>
    <html lang=“en”>
    <head>
        <meta charset=“UTF-8”>
        <title>promis 实验</title>
        <script src=“http://gagalulu.wang/blog/js/jquery.js”>
        </script>
        <script src=“src/js/promis.js”></script>
        <script src=“src/js/request.js”></script>
        <script src=“src/js/index.js”></script>
        <style>
            *{
                margin: 0;
                padding: 0;
            }
            body{
                background:#333;
            }
            .main{
                width:100%;
                color:#fff;
            }
            .main .desc{
                width:90%;
                margin:25px auto;
                padding: 10px;
                border: 1px solid #3f3f3f;
            }
            .main .ctrl{
                width:90%;
                margin: 20px auto;
    
            }
            .main .ctrl .btn{
                color: #fff;
                border: 1px solid #3f3f3f;
                padding: 10px;
                border-radius:5px;
                cursor: pointer;
            }
            .main .log{
                width:90%;
                margin: 20px auto;
                min-height:300px;
                border: 1px solid #3f3f3f;
                padding: 10px;
            }
        </style>
    
    </head>
    <body>
        <div class=“main”>
            <div class=“desc”>
                <p>程序员小王想娶女神小米。</p>
                <p>小米说必须先让她爸爸认可,然后让妈妈认可,然后让大伯认可,且让妈妈认可的前提是爸爸认可,让大伯认可的前提是爸爸妈妈都认可,然后我在考虑~</p>
                <p>假设:他们各个人同意的可能性都为80%,而且都必须有一段时间考虑,每个人可以请求两次。</p>
                <p>问:小王能不能取到小米~?</p>
            </div>
            <div class=“ctrl”>
                <span class=“btn” id=“beginBtn”>开始求婚</span>
            </div>
            <div class=“log” id=“logFrame”>
                <p>程序员小王想娶女神小米。</p>
            </div>
        </div>
    </body>
    </html>

    点击此处查看效果

    对应的代码已经提交到github,大家可以fork观赏,有任何问题,欢迎在下面留言。

    github地址:git@github.com:gagaprince/promis.git

    转载请注明出处:http://gagalulu.wang/blog/detail/20 您的支持是我最大的动力!