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;
对于需要在里面带上参数的情况我想大部分应该存在在数据请求上
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。