Promise 是异步编程的一种解决方案,比传统的解决方案–回调函数和事件更加高效,它开始是在node社区提出和实现,后面ES6将其写进了语言标准,统一了语法,原生提供了Promise;
promise听着很陌生,其实我们一直都在使用
那么promise是什么呐?首先字面意思翻译就知道,保证,承诺的意思;也就是说时间是未来;好比说---我以后给你怎么怎么样---,接合上面的那句"比传统的解决方案–回调函数和事件更加高效"就知道个大概了;“承诺将来会执行”的对象(或者说方法,函数,函数本质也是个对象)在JavaScript中称为Promise对象。最典型的就是ajax;ajax就是承诺将会在请求结束后给你个结果,不管是成功或者失败;

js是一门单线程的语言,无法做到那些强语言的多线程操作,只因为js的宿主环境是浏览器,所以有时候看起来像是多线程;
由于这个“单线程缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现;比方在一段动画结束之后弹出个窗口

obj.animate({
    width:200
},300,function(){
    alert(1)
})

先说一下普通的回调函数的不足之处,如果只是像上面的那样的操作用这个回调操作也是很愉快的,但是又没想过,假设有一串任务队列呢,做完第一件事之后去做第二,第三...第N件事情呢;那么就会出现这样的现象

//三个简单的div,我想让第一个向右移动到600px处之后,第二个开始移动到600px,在第三个....
<div class="a1"></div>
<div class="a2"></div>
<div class="a3"></div>
//假设用传统的回调
    var disx=a1.offset().left;
        var timer=setInterval(function(){
            if(disx<=600){
                disx+=30;
                a1.css({
                    left:disx
                })
            }
            else{
                clearInterval(timer);
                var disx2=a2.offset().left;
                var timer2=setInterval(function(){
                    if(disx2<=600){
                        disx2+=30;
                        a2.css({
                            left:disx2
                        })
                    }
                    else{
                        clearInterval(timer2);
                        var disx3=a3.offset().left;
                        var timer3=setInterval(function(){
                            if(disx3<=600){
                                disx3+=30;
                                a3.css({
                                    left:disx3
                                })
                            }
                            else{
                                clearInterval(timer3);
                            }
                        }, 30)
                    }
                },30)
            }
        }, 30);

可以看到左侧已经空出来了一大块'三角形'了,假设n个呢...那就有些崩溃了;回调会越来越多,宛如地狱一般,所以这种现象被称之为"回调地狱",就像地狱一般无法自拔

那么promise可以干什么呢,他可以把事件操作和事件结果处理分开,我们可以不关心事件操作,直关心操作结果,成功我们就走成功,失败就走失败,我知道你现在心里一定在想jquery的ajax,没错,其实那就是封装好的一个promise;

那好,一个ajax

$.ajax({
    url: '/path/to/file',
    type: 'default GET (Other values: POST)',
    dataType: 'default: Intelligent Guess (Other values: xml, json, script, or html)',
    data: {param1: 'value1'},
})
.done(function(data) {
    console.log("success");//成功走这儿
})
.fail(function(err) {
console.log("error"); //失败走这儿
})
.always(function() {
console.log("complete"); //不管成功失败都会走这儿
});

从上面的就可以看出貌似和平时的有些不一样;我们对于结果而言只需要关注在后面.done(),.fail(),.always()

因为有ajax这个很熟悉的操作,所以我们会先入为主的认为这个promise就是拿来干ajax的事情的...

其实不然,别忘了最上面的第一句话"Promise 是异步编程的一种解决方案" js的异步可不单单是ajax,
1、定时器都是异步操作
2、事件绑定都是异步操作
3、AJAX中一般我们都采取异步操作(也可以同步)
4、回调函数可以理解为异步(不是严谨的异步操作)

然后来一个和定时器接合的promise的例子

    var p=new Promise(function(resolve,reject){
        console.log(1)
        setTimeout(function(){
            var n=parseInt(Math.random()*10);
            if(n>5){
                resolve(n)
            }
            else{
                reject(n)
            }
        },3000);
    })
    p.then(function(num){
        console.log('成功了,这个数是 ', num)
    }).catch(function(num){
        console.log('失败了,这个数是 ',num)
    });

结果: 1 ,然后3s之后得到成功了,这个数是, 6

从这个东西我们至少可以得到几个关键信息
1,promise不单单是用在ajax上的,所有异步操作我们都可以用;
2,promise接受一个函数resolver作为参数,而这个函数又接受2个函数作为参数,这两个函数分别对应成功和失败的操作逻辑
3,成功和失败由我们自己来定义 ajax有一个既定事实,东西没搞回来我们一般认为他失败了,搞回来了我们则认为他成功了,如果从前面的那'成功和失败由我们自己来定义 '来说,我们也可以说东西没回来是成功的,回来了则是失败,只是没人这么干而已;
4,不管这么样,他内部的操作总是会跑一次,也就是console.log(1);所以看起来貌似没调用函数但是也跑了一次
5,注入的两个参数resolve,reject在外面一定有对应的操作等着

注意到那个then,catch了嘛?这就是promise的另一半,操作完成后我们需要进行的操作;这不用讲大家都明白;
就是这东西的写法挺多的,比如

var p=new Promise(function(resolve,reject){
    //.......
});
p.then(function(){
    //...
},function(){
    //...这样就没了catch();
})

在用jquery的ajax说,一般常用的

$.ajax({
    url:'xx',
    success:function(data){
        //ok
    },
    error:function(err){
        //err
    }
})

上面你看到了那东西还有done,fail...
比较贴近promise的还可以这么干

$.ajax({//...}).then().catch()

其实多写一点儿就可以看到这个东西就是个promise对象,

var p=$.ajax({//...});
log(p);//Promise {<pending>},你可以展开Promise看多更多内部东西
p.then().catch()

好,又出现了新的单词
pending(在…期间,直到…时为止;);处于事件进行中,等待结果中;
resolve(决心要做…);出好结果了,我应该做正确的事;
reject(拒绝);出坏结果了,我应该做坏结果对应的事;
这哥仨一下就被联想在了一起,
没错,这正是promise的三种状态,等待,成功,失败;其中成功失败是由等待转变,这一过程不可逆;

有时候经常会遇到连续依赖调用,先请求第一个接口,然后接收到返回的一些数据,然后利用得到的数据,在去请求第二个接口,然后又接收传回来的数据,然后在去请求第三个接口....你可能会得到这样结构

    $.ajax({
        url:'example/1',
        type:'GET',
        data:xx,
        ....
        success:function(data){
            $.ajax({
                url:data.url,
                type:'GET',
                data:xx,
                ....
                success:function(data){
                    $.ajax({
                        url:'example/2',
                        type:'GET',
                        data:{'name':data.name}
                        ....
                        success:function(data){
                            console.log(data)
                        }
                    })
                }
            })
        }
    })

当然,你可能会有好一些的办法,现在promise可以这样做,假设我有一个url.json文件里面存有一个接口,我先去得到这个json文件,得到之后在根据它里面的内容再去请求...

    function getdata(url){
        var p=new Promise(function(resolve,reject){
            $.ajax({
                url: url,
                type:"GET",
                datatype:"jsonp",
                success: function (d) {
                   resolve(d)
                },
                error:function(err){
                    reject(err)
                }
            });
        })
        return p;
    }

    var p1=getdata('url.json');
    p1.then(function(data){
        getdata(data.url)
    }).then(function(data){
        console.log(data)
    }).catch(function(err){
        console.log(err)
    })

;
这是一个今日头条的热词接口,先不管下面的报错,那个是跨域造成的,反正就结果而言,东西是拿回来了也可以看到确实发出了2个请求,但是在写法上好了很多;

promise的链式写法和jquery很像,jquery之所以可以不停点下去,也在于他每一次操作之后都会返回一个dom对象,这个promise也一样,每一次都会返回一个promise对象,构成了链式调用...

除了这种链式调用场景,还可能会遇到一次性要发出多个请求,emm 也就是并行异步任务了,普通方法就随便怎么自由实现了,看一下promise的做法吧

    function getText(url){
        var p=new Promise(function(resolve,reject){
            $.ajax({
                url:url,
                type:'get',
                datatype:'html',
                success:function(data){
                    resolve(data)
                },
                error:function(err){
                    reject(err)
                }
            })
        });
        return p;
    }
    var get1=getText('../c.html');
    var get2=getText('../b.html');
    Promise.all([get1,get2]).then(function(data){
        console.log(data)
    })


同时执行ge1t和get2,并在它们都完成后执行then,最后结果被装载了一个数组,分别对应各自的请求;

Promise 的用法确实变化太多,因为只是初尝,所以很多地方还是懵逼状态,只留下了最基本的用法

补充:之前一直对resolve存在不少疑惑,包括现在也是,resolve和reject只是两个状态切换器,可以这么说,promise里面的代码都是处于pending状态,是正在执行中的;而代码一旦遇到resolve或者reject就说明到了有了结果了,你可以在你任何想要让pending代码状态转换的时候使用两个抑制力,而且resolve/reject其实不一定要带上参数,比方你只需要明确这里是标志有结果了,我应该执行下一步了,那么你就可以使用抑制力了

比方一个例子,点击某个按钮,让处于屏幕外的元素依次运动到屏幕里面来

//这几个按钮一开始是处于屏幕外面的
<div class="kechenzhixun-btn kechenzhixun-btn-a" >课程咨询</div>
    <div class="kechen-zhixun">
        <div class="zhixun-bnt-group">
            <div style="color:white;font-size:14px;margin-bottom:18px">选择对应课程</div>
            <div class="kechenzhixun-btn kechenzhixun-btn-b kechenzhixun-btn-b-1">产品课程</div>
            <div class="kechenzhixun-btn kechenzhixun-btn-b kechenzhixun-btn-b-2">运营课程</div>
            <div class="kechenzhixun-btn kechenzhixun-btn-b kechenzhixun-btn-b-3">专项技能班</div>
            <div class="kechenzhixun-btn kechenzhixun-btn-b kechenzhixun-btn-b-4 close-kechen-zhixun"> 关闭</div>
        </div>
    </div>

我们使用promise来实现这个功能

function move(obj){
    return new Promise(function(resolve,reject){
        var tagObj=document.querySelector('.'+obj);
        var timer;
        if(parseInt(tagObj.offsetLeft)>0){
            timer=setInterval(function(){
                tagObj.style.left=tagObj.offsetLeft-50+'px';
                if(parseInt(tagObj.offsetLeft)<0){
                    clearInterval(timer);
                    resolve();
                }
            },41)
        }else{
            timer1=setInterval(function(){
                tagObj.style.left=tagObj.offsetLeft+30+'px';
                if(parseInt(tagObj.offsetLeft)>140){
                    clearInterval(timer1);
                    resolve();
                }
            },30)
        }
    })
}

上面就是一个简单的例子,点击依次出现,再点击依次退出去,在这里的两个resolve里面就并没有带参数,这里仅仅用作标识,代表状态转换,从pending转换到了resolve;
对于需要在里面带上参数的情况我想大部分应该存在在数据请求上


墨韵
109 声望0 粉丝