1.什么是Promise
Promise 是異步編程的一種解決方案郑口,其實是一個構(gòu)造函數(shù),自己身上有all各淀、reject懒鉴、resolve這幾個方法,原型上有then碎浇、catch等方法临谱。
Promise對象有以下兩個特點:
1)對象的狀態(tài)不受外界影響。Promise對象代表一個異步操作奴璃,有三種狀態(tài):pending(進(jìn)行中)悉默、fulfilled(已成功)和rejected(已失敗)苟穆。只有異步操作的結(jié)果抄课,可以決定當(dāng)前是哪一種狀態(tài),任何其他操作都無法改變這個狀態(tài)雳旅。這也是Promise這個名字的由來跟磨,它的英語意思就是“承諾”,表示其他手段無法改變攒盈。
2)一旦狀態(tài)改變抵拘,就不會再變,任何時候都可以得到這個結(jié)果型豁。Promise對象的狀態(tài)改變僵蛛,只有兩種可能:從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected。只要這兩種情況發(fā)生迎变,狀態(tài)就凝固了充尉,不會再變了,會一直保持這個結(jié)果氏豌,這時就稱為 resolved(已定型)喉酌。如果改變已經(jīng)發(fā)生了,你再對Promise對象添加回調(diào)函數(shù)泵喘,也會立即得到這個結(jié)果。這與事件(Event)完全不同般妙,事件的特點是纪铺,如果你錯過了它,再去監(jiān)聽碟渺,是得不到結(jié)果的鲜锚。
下面先 new一個Promise
let p = new Promise(function(resolve, reject){
??? //做一些異步操作
??? setTimeout(function(){
??????? console.log('執(zhí)行完成Promise');
??????? resolve('要返回的數(shù)據(jù)可以任何數(shù)據(jù)例如接口返回數(shù)據(jù)');
???? }, 2000);
});
刷新頁面會發(fā)現(xiàn)控制臺直接打出
其執(zhí)行過程是:執(zhí)行了一個異步操作,也就是setTimeout,2秒后芜繁,輸出“執(zhí)行完成”旺隙,并且調(diào)用resolve方法。
注意骏令!我只是new了一個對象蔬捷,并沒有調(diào)用它,我們傳進(jìn)去的函數(shù)就已經(jīng)執(zhí)行了榔袋,這是需要注意的一個細(xì)節(jié)周拐。所以我們用Promise的時候一般是包在一個函數(shù)中,在需要的時候去運行這個函數(shù)凰兑,如
<div onClick={promiseClick}>開始異步請求</div>
const promiseClick =()=>{
??? console.log('點擊方法被調(diào)用')
??? let p = new Promise(function(resolve, reject){
??????? //做一些異步操作
??????? setTimeout(function(){
???????????? console.log('執(zhí)行完成Promise');
???????????? resolve('要返回的數(shù)據(jù)可以任何數(shù)據(jù)例如接口返回數(shù)據(jù)');
??????? }, 2000);
?? });
? ? ? ? return p
}
刷新頁面的時候是沒有任何反映的妥粟,但是點擊后控制臺打出
當(dāng)放在函數(shù)里面的時候只有調(diào)用的時候才會被執(zhí)行
那么,接下里解決兩個問題:
1吏够、為什么要放在函數(shù)里面
2勾给、resolve是個什么鬼
我們包裝好的函數(shù)最后,會return出Promise對象锅知,也就是說播急,執(zhí)行這個函數(shù)我們得到了一個Promise對象。接下來就可以用Promise對象上有then喉镰、catch方法了旅择,這就是Promise的強大之處了,看下面的代碼:
promiseClick().then(function(data){
??? console.log(data);
??? //后面可以用傳過來的數(shù)據(jù)做些其他操作
});
這樣控制臺輸出
先是方法被調(diào)用起床執(zhí)行了promise,最后執(zhí)行了promise的then方法侣姆,then方法是一個函數(shù)接受一個參數(shù)是接受resolve返回的數(shù)據(jù)這事就輸出了‘要返回的數(shù)據(jù)可以任何數(shù)據(jù)例如接口返回數(shù)據(jù)’
這時候你應(yīng)該有所領(lǐng)悟了生真,原來then里面的函數(shù)就跟我們平時的回調(diào)函數(shù)一個意思,能夠在promiseClick這個異步任務(wù)執(zhí)行完成之后被執(zhí)行捺宗。這就是Promise的作用了柱蟀,簡單來講,就是能把原來的回調(diào)寫法分離出來蚜厉,在異步操作執(zhí)行完后长已,用鏈?zhǔn)秸{(diào)用的方式執(zhí)行回調(diào)函數(shù)。
你可能會覺得在這個和寫一個回調(diào)函數(shù)沒有什么區(qū)別昼牛;那么术瓮,如果有多層回調(diào)該怎么辦?如果callback也是一個異步操作贰健,而且執(zhí)行完后也需要有相應(yīng)的回調(diào)函數(shù)胞四,該怎么辦呢?總不能再定義一個callback2伶椿,然后給callback傳進(jìn)去吧辜伟。而Promise的優(yōu)勢在于氓侧,可以在then方法中繼續(xù)寫Promise對象并返回,然后繼續(xù)調(diào)用then來進(jìn)行回調(diào)操作导狡。
所以:精髓在于:Promise只是能夠簡化層層回調(diào)的寫法约巷,而實質(zhì)上,Promise的精髓是“狀態(tài)”旱捧,用維護(hù)狀態(tài)独郎、傳遞狀態(tài)的方式來使得回調(diào)函數(shù)能夠及時調(diào)用,它比傳遞callback函數(shù)要簡單廊佩、靈活的多囚聚。所以使用Promise的正確場景是這樣的:
promiseClick()
.then(function(data){
? ? console.log(data);
? ? return runAsync2();
})
.then(function(data){
? ? console.log(data);
? ? return runAsync3();
})
.then(function(data){
? ? console.log(data);
})
這樣能夠按順序,每隔兩秒輸出每個異步回調(diào)中的內(nèi)容标锄,在runAsync2中傳給resolve的數(shù)據(jù)顽铸,能在接下來的then方法中拿到。
reject的用法
以上是對promise的resolve用法進(jìn)行了解釋料皇,相當(dāng)于resolve是對promise成功時候的回調(diào)谓松,它把promise的狀態(tài)修改為
fullfiled,那么践剂,reject就是失敗的時候的回調(diào)鬼譬,他把promise的狀態(tài)修改為rejected,這樣我們在then中就能捕捉到逊脯,然后執(zhí)行“失敗”情況的回調(diào)优质。
functionpromiseClick(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的隨機數(shù)
console.log('隨機數(shù)生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('數(shù)字太于10了即將執(zhí)行失敗回調(diào)');
}
}, 2000);
? })
? return p
? }
promiseClick().then(
function(data){
console.log('resolved成功回調(diào)');
console.log('成功回調(diào)接受的值:',data);
},
function(reason, data){
console.log('rejected失敗回調(diào)');
console.log('失敗執(zhí)行回調(diào)拋出失敗原因:',reason);
}
);
執(zhí)行結(jié)果:
以上代碼:調(diào)用promiseClick方法執(zhí)行,2秒后獲取到一個隨機數(shù)军洼,如果小于10巩螃,我們算成功,調(diào)用resolve修改Promise的狀態(tài)為fullfiled匕争。否則我們認(rèn)為是“失敗”了避乏,調(diào)用reject并傳遞一個參數(shù),作為失敗的原因甘桑。并將狀態(tài)改成rejected
運行promiseClick并且在then中傳了兩個參數(shù)拍皮,這兩個參數(shù)分別是兩個函數(shù),then方法可以接受兩個參數(shù)跑杭,第一個對應(yīng)resolve的回調(diào)铆帽,第二個對應(yīng)reject的回調(diào)。(也就是說then方法中接受兩個回調(diào)德谅,一個成功的回調(diào)函數(shù)锄贼,一個失敗的回調(diào)函數(shù),并且能在回調(diào)函數(shù)中拿到成功的數(shù)據(jù)和失敗的原因)女阀,所以我們能夠分別拿到成功和失敗傳過來的數(shù)據(jù)就有以上的運行結(jié)果
catch的用法
與Promise對象方法then方法并行的一個方法就是catch,與try? catch類似宅荤,catch就是用來捕獲異常的,也就是和then方法中接受的第二參數(shù)rejected的回調(diào)是一樣的浸策,如下:
function promiseClick(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的隨機數(shù)
console.log('隨機數(shù)生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('數(shù)字太于10了即將執(zhí)行失敗回調(diào)');
}
}, 2000);
? })
? return p
? }
promiseClick().then(
function(data){
console.log('resolved成功回調(diào)');
console.log('成功回調(diào)接受的值:',data);
}
)
.catch(function(reason, data){
console.log('catch到rejected失敗回調(diào)');
console.log('catch失敗執(zhí)行回調(diào)拋出失敗原因:',reason);
})
執(zhí)行結(jié)果:
效果和寫在then的第二個參數(shù)里面一樣冯键。它將大于10的情況下的失敗回調(diào)的原因輸出,但是庸汗,它還有另外一個作用:在執(zhí)行resolve的回調(diào)(也就是上面then中的第一個參數(shù))時惫确,如果拋出異常了(代碼出錯了),那么并不會報錯卡死js蚯舱,而是會進(jìn)到這個catch方法中改化。如下:
function promiseClick(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的隨機數(shù)
console.log('隨機數(shù)生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('數(shù)字太于10了即將執(zhí)行失敗回調(diào)');
}
}, 2000);
? })
? return p
? }
promiseClick().then(
function(data){
console.log('resolved成功回調(diào)');
console.log('成功回調(diào)接受的值:',data);
console.log(noData);
}
)
.catch(function(reason, data){
console.log('catch到rejected失敗回調(diào)');
console.log('catch失敗執(zhí)行回調(diào)拋出失敗原因:',reason);
});
在resolve的回調(diào)中,我們console.log(noData);而noData這個變量是沒有被定義的枉昏。如果我們不用Promise陈肛,代碼運行到這里就直接在控制臺報錯了,不往下運行了兄裂。但是在這里句旱,會得到上圖的結(jié)果,也就是說進(jìn)到catch方法里面去了晰奖,而且把錯誤原因傳到了reason參數(shù)中谈撒。即便是有錯誤的代碼也不會報錯了
all的用法
與then同級的另一個方法,all方法匾南,該方法提供了并行執(zhí)行異步操作的能力啃匿,并且在所有異步操作執(zhí)行完后并且執(zhí)行結(jié)果都是成功的時候才執(zhí)行回調(diào)。
將上述方法復(fù)制兩份并重命名promiseClick3(), promiseClick2(), promiseClick1()蛆楞,如下
function promiseClick1(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的隨機數(shù)
console.log('隨機數(shù)生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('數(shù)字太于10了即將執(zhí)行失敗回調(diào)');
}
}, 2000);
? })
? return p
? }
? function promiseClick2(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的隨機數(shù)
console.log('隨機數(shù)生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('數(shù)字太于10了即將執(zhí)行失敗回調(diào)');
}
}, 2000);
? })
? return p
? }
? function promiseClick3(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的隨機數(shù)
console.log('隨機數(shù)生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('數(shù)字太于10了即將執(zhí)行失敗回調(diào)');
}
}, 2000);
? })
? return p
? }
Promise
.all([promiseClick3(), promiseClick2(), promiseClick1()])
.then(function(results){
console.log(results);
})
Promise.all來執(zhí)行溯乒,all接收一個數(shù)組參數(shù),這組參數(shù)為需要執(zhí)行異步操作的所有方法臊岸,里面的值最終都算返回Promise對象橙数。這樣,三個異步操作的并行執(zhí)行的帅戒,等到它們都執(zhí)行完后才會進(jìn)到then里面灯帮。那么,三個異步操作返回的數(shù)據(jù)哪里去了呢逻住?都在then里面钟哥,all會把所有異步操作的結(jié)果放進(jìn)一個數(shù)組中傳給then,然后再執(zhí)行then方法的成功回調(diào)將結(jié)果接收瞎访,結(jié)果如下:(分別執(zhí)行得到結(jié)果腻贰,all統(tǒng)一執(zhí)行完三個函數(shù)并將值存在一個數(shù)組里面返回給then進(jìn)行回調(diào)輸出):
這樣以后就可以用all并行執(zhí)行多個異步操作,并且在一個回調(diào)中處理所有的返回數(shù)據(jù)扒秸,比如你需要提前準(zhǔn)備好所有數(shù)據(jù)才渲染頁面的時候就可以使用all,執(zhí)行多個異步操作將所有的數(shù)據(jù)處理好播演,再去渲染
race的用法
all是等所有的異步操作都執(zhí)行完了再執(zhí)行then方法冀瓦,那么race方法就是相反的,誰先執(zhí)行完成就先執(zhí)行回調(diào)写烤。
我們將上面的方法延遲分別改成2,3,4秒
function promiseClick1(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的隨機數(shù)
console.log('2s隨機數(shù)生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('2s數(shù)字太于10了即將執(zhí)行失敗回調(diào)');
}
}, 2000);
? })
? return p
? }
? function promiseClick2(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的隨機數(shù)
console.log('3s隨機數(shù)生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('3s數(shù)字太于10了即將執(zhí)行失敗回調(diào)');
}
}, 3000);
? })
? return p
? }
? function promiseClick3(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的隨機數(shù)
console.log('4s隨機數(shù)生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('4s數(shù)字太于10了即將執(zhí)行失敗回調(diào)');
}
}, 4000);
? })
? return p
? }
Promise
.race([promiseClick3(), promiseClick2(), promiseClick1()])
.then(function(results){
console.log(results);
},function(reason){
console.log(reason);
});
當(dāng)2s后promiseClick1執(zhí)行完成后就已經(jīng)進(jìn)入到了then里面回調(diào)翼闽,在then里面的回調(diào)開始執(zhí)行時,promiseClick2()和promiseClick3()并沒有停止洲炊,仍舊再執(zhí)行感局。于是再過3秒后,輸出了他們各自的回調(diào)值
race的使用比如可以使用在一個請求在10s內(nèi)請求成功的話就走then方法暂衡,如果10s內(nèi)沒有請求成功的話進(jìn)入reject回調(diào)執(zhí)行另一個操作询微。