JavaScript---Promise
什么是Promise?
Promise是ES6新增的对象。他是构造函数,通过构造实例来使用他的方法。 var p = new Promise();
Promise是干什么用的?
用来传递异步操作的消息(如ajax请求),它代表了某个未来才会知道结果的事件(通常是一个异步操作)
Promise的3种状态:
1.Pending(未完成)---译为‘即将发生的’,可以理解为Promise对象创建实例的初始状态;
2. Resolved(成功)---译为‘已解决的’,可以理解为成功的状态;
3.Rejected(失败)--译为‘拒绝的’,可以理解为失败的状态。
Promise对象的特点:
1.Promise的状态不受外界影响。只有异步操作的结果,才可以决定当前是什么状态,其他任何操作都无法改变他的状态。
2.一旦状态发生改变,就不会再变,任何时候都可以得到这个结果。Promise对象的的状态改变,只有两种情况:1.Pending变为Resolved, 2.Pending变为Rejected。只要这两种情况发生其中一个,另一个是不会发生的。因为状态会凝固,即使你还想添加回调函数试图改变结果,也不起作用。
什么时候用promise?:函数回调多层嵌套(有依赖关系)
来看一个demo:多个ajax的请求(存在依赖)
常用的方式:使用函数嵌套
$.ajax({
url:'http://localhost/api/req1.php',
success:function(str1){
console.log(str1);
$.ajax({
url:'http://localhost/api/req2.php',
success:function(str2){
console.log(str2);
$.ajax({
url:'http://localhost/api/req3.php',
success:function(str3){
console.log(str3);
document.body.innerHTML = str1 + str2 + str3
}
})
}
})
}
})
ajax的多层嵌套会造成阻塞,只有前一个请求成功了,下一个请求才会开始执行,万一前一个请求执行失败,下一个请求永远无法执行,造成页面‘假死’。
再来看promise是如何处理的?
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 方法和 reject 方法。
如果异步操作成功,则用 resolve 方法将 Promise 对象的状态,从「未完成」变为「成功」(即从 pending 变为 resolved)。
如果异步操作失败,则用 reject 方法将 Promise 对象的状态,从「未完成」变为「失败」(即从 pending 变为 rejected)。
var p1 = new Promise(function(resolve,reject){
// 这时的状态:pending
$.ajax({
url:'http://localhost/api/req1.php',
success:function(str1){
// console.log(str1);
//把promise对象的状态改成resolved
//只要状态被修改成resolved,就不会再改变
resolve(str1);
}
})
});
var p2 = new Promise(function(resolve,reject){
$.ajax({
url:'http://localhost/api/req2.php',
success:function(str2){
// console.log(str2);
resolve(str2);
}
})
});
var p3 = new Promise(function(resolve,reject){
$.ajax({
url:'http://localhost/api/req3.php',
success:function(str3){
// console.log(str3);
resolve(str3)
}
});
});
// 等3个请求都成功后
// 把数据写入页面
var pAll = Promise.all([p1,p2,p3]);
pAll.then(function(data){
console.log('所有:',data);
});
// 返回第一个完成的数据
var pFast = Promise.race([p1,p2,p3]);
pFast.then(function(data){
console.log('手速最快:',data);
});
再来看具体的实例:
比如:获取当前城市的天气预报API-->获取当前城市API-->获取当前城市的ip地址API
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>多个ajax请求</title> <script src="js/jquery-3.1.1.js"></script> <script> $(function(){ /* * http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=58.248.240.46 * http://ip.taobao.com/service/getIpInfo.php?ip=58.248.240.46 ajax依赖数据解决方案 * ajax嵌套 * 层级太深,难以维护 * 同步请求 * 影响其他代码的执行 * ES6 Promise * */ var currentCity; // 通过IP获取当前城市 var cityPromise = new Promise(function(resolve,reject){ $.ajax({ url:'../getCity.php', // async:false, dataType:'json', success:function(data){ console.log(data); currentCity = data.data.city.replace(/市$/,''); console.log(currentCity); resolve(currentCity); }, error:function(){ reject('请求失败') } }); }); cityPromise.then(function(city){ console.log('成功',city); $.ajax({ url:'http://wthrcdn.etouch.cn/weather_mini', dataType:'json', data:{city:currentCity}, success:function(data){ console.log(data) } }); }, function(){ console.log('失败') }); }) </script> </head> <body> </body> </html>
在chrome中显示:
补充:Promise对象的内置方法
Promise.all(p1,p2,p3...)---将多个Promire 实例包装成一个新的Promise实例 注:所有的参数中的promise状态都为resolved时,新的promise状态才为
resolved,一个是rejected,新的promise状态为rejected。
Promise.race()---竞速模式 返回最先执行完的resolved结果。
原型方法:Promise.prototype.then(successFn[,failFn]) ---Promise实例生成以后,可以用then方法分别指定Resolved状态和Rejected状态的回调函数。并根据Promise对象的状态来确定执行的操作:
1.resolved时执行第一个函数successFn。
2.rejected时执行第二个函数failFn。
var p = new Promise(function(resolve, reject){
//ajax请求
ajax({
url:'xxx.php',
success:function(data){
resolve(data)
},
fail:function(){
reject('请求失败')
}
});
});
//指定Resolved状态和Rejected状态的回调函数
//一般用于处理数据
p.then(function(res){
//这里得到resolve传过来的数据
},function(err){
//这里得到reject传过来的数据
})
Promise对象就暂告一段落, 细节以后慢慢补充
2017-3-27 15:20
---------------------------------------------------------------------------------更新!!!!----------------------------------------------------------------------------
前天面试的时候,面试官有问到promise用法,但是好久没用了。竟然忘了!!!!! 这充分说明了,回顾知识是多么重要!!! 你永远不知道,你下一秒会不会用到。
回到正题:
前面说道,创建一个promise对象,只需“new”一个就好了。
var p1 = new Promise(function(resolve,reject){ setTimeout(function () { alert('666'); },2000); })
试着执行这一段代码,2秒后弹出框“666”。 感觉有点怪怪的,我只是“new”出来一个promse对象而已,并没有调用啊,他怎么自动执行了?注意这个细节。一般地,promise对象放到一个函数里面,不要让他自动执行。
function runAsync() { var p1 = new Promise(function (resolve, reject) {
// 异步操作的代码 setTimeout(function () { alert('666'); }, 2000); }); return p1 } runAsync();
上面的写法,返回实例promise。然后直接可以调用then() 方法了。 then方法接收一个函数作为参数,并且会拿到我们在runAsync中调用resolve时传的的参数。
来,感受一下:
function runAsync() { var p1 = new Promise(function (resolve, reject) { setTimeout(function () { console.log('666'); var obj = { name:'jjk', age:18 } resolve(obj) }, 2000); }); return p1 } runAsync().then(function(data){ console.log(data); })
在chrome中显示:
then里面的函数就跟我们平时的回调函数一个意思,能够在runAsync这个异步任务执行完成之后被执行。这就是Promise的作用了,简单来讲,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。看到这里,疑问就来了,回调函数不是也可以做到这一点么?
function runAsync(callback){ setTimeout(function () { console.log('666'); var obj = { name:'jjk', age:18 } callback(obj); }, 2000); } runAsync(function(data){ console.log(data); })
看来Promise不怎么样啊。别急,试想一下,有多层回调该怎么办?如果callback也是一个异步操作,而且执行完后也需要有相应的回调函数,该怎么办呢?总不能再定义一个callback2,然后给callback传进去吧。而Promise的优势在于,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作。从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。所以使用Promise的正确场景是这样的:
function runAsync1() { var p1 = new Promise(function (resolve, reject) { setTimeout(function () { console.log('111'); resolve('第一次') }, 2000); }); return p1 }; function runAsync2() { var p2 = new Promise(function (resolve, reject) { setTimeout(function () { console.log('222'); resolve("第二次") }, 2000); }); return p2 }; function runAsync3() { var p3 = new Promise(function (resolve, reject) { setTimeout(function () { console.log('333'); resolve("第三次") }, 2000); }); return p3 }; runAsync1().then(function (data) { console.log(data); return runAsync2() }) .then(function (data) { console.log(data); return runAsync3() }) .then(function (data) { console.log(data); })
在chrome中显示:
在then方法里面直接返回promise实例,然后继续链式调用。那么,如果我不返回实例,而是直接返回数据,还能链式调用吗?
function runAsync1() { var p1 = new Promise(function (resolve, reject) { setTimeout(function () { console.log('111'); resolve('第一次') }, 2000); }); return p1; }; function runAsync2() { var p2 = new Promise(function (resolve, reject) { setTimeout(function () { console.log('222'); resolve("第二次") }, 2000); }); return p2; }; function runAsync3() { var p3 = new Promise(function (resolve, reject) { setTimeout(function () { console.log('333'); resolve("第三次") }, 2000); }); return p3; }; runAsync1().then(function (data) { console.log(data); return '直接返回数据看看' }) .then(function (data) { console.log(data); return runAsync3() }) .then(function (data) { console.log(data); })
在chrome中显示
结果显而易见,返回数据之后,还可以调用then方法,可then方法不是promise的方法吗? 答案是这样的:then方法返回数据时,会把数据包装成promise对象,以供下一个then调用。
事实上,我们前面的例子都是只有“执行成功”的回调,还没有“失败”的情况,reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调。
function runAsync() { var p1 = new Promise(function (resolve, reject) { setTimeout(function () { var num = Math.ceil(Math.random() * 10); if (num <= 5) { resolve(num); } else { reject('warming') } }, 1000) }); return p1; } runAsync().then(function (data) { console.log('resolved'); console.log(data); }, function (reason, data) { console.log('rejected'); console.log(reason); console.log(data); })
在chrome中显示:
或者
可以看出then方法接收2个函数作为参数,第一个参数是状态成功时执行,第二个参数是状态失败后执行,该函数的第一个参数就是reject的数据。
说完了then方法,还要说说其他方法
catch,它是做什么用的呢?其实它和then的第二个参数一样,用来指定reject的回调,用法是这样:
function runAsync() { var p1 = new Promise(function (resolve, reject) { setTimeout(function () { var num = Math.ceil(Math.random() * 10); if (num <= 5) { resolve(num); } else { reject('warming') } }, 1000) }); return p1; } runAsync() .then(function (data) { console.log('resolved'); console.log(data); }) .catch(function(err){ console.log('rejected'); console.log(err); })
不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。来看代码:
function runAsync() { var p1 = new Promise(function (resolve, reject) { setTimeout(function () { var num = Math.ceil(Math.random() * 10); if (num <= 5) { resolve(num); } else { reject('warming') } }, 1000) }); return p1; } runAsync() .then(function (data) { console.log('resolved'); console.log(data); console.log(num); }) .catch(function(err){ console.log('rejected'); console.log(err); })
当进入resolve状态时,在chrome中显示:
这个跟try/catch 类似。