熟悉ES6或者在開發(fā)中使用過Axios或者Fetch的同學(xué)一定聽過Promise,因?yàn)橐陨蟽煞N異步請(qǐng)求庫都是基于Promise對(duì)象對(duì)Ajax進(jìn)行的封裝,那么你對(duì)Promise了解嗎?
什么是Promise
首先Promise不是ES6提出來的,Promise是一種異步編程的解決方案,只是ES6對(duì)其進(jìn)行了語法規(guī)范,首先大家應(yīng)該清楚,JS是單線程的語言,但又因?yàn)槠渌拗鳝h(huán)境(瀏覽器或Node)是多線程的,所以JS實(shí)現(xiàn)異步編程和JS的單線程之間并不存在矛盾,傳統(tǒng)中,我們會(huì)用回調(diào)函數(shù)或者事件來實(shí)現(xiàn)JS的異步編程,但Promise的提出和實(shí)現(xiàn),使JS的異步編程變得更加強(qiáng)大。
Promise其實(shí)是一個(gè)容器函數(shù),里面包含著未來才會(huì)知道的結(jié)果(比如http請(qǐng)求或者其他異步調(diào)用的方法)。這些異步操作可以決定Promise對(duì)象的狀態(tài),Promise有且僅有三種狀態(tài)分別為pending(進(jìn)行中)、fulfilled(已成功)、rejected(已失敗)。且Promise的這三種狀態(tài)只能由其中包含的異步操作的結(jié)果來決定。到底哪種結(jié)果決定什么樣的狀態(tài),這是和你的編程邏輯有關(guān)。
這里需要舉個(gè)例子:大家都知道,當(dāng)Axios請(qǐng)求成功的時(shí)候(Axios內(nèi)部封裝的請(qǐng)求成功指調(diào)通后臺(tái)接口,不是http狀態(tài)為200),是返回一個(gè)Promise對(duì)象,這時(shí)候,我們可以用 then來指定其成功后,接下來的回調(diào)函數(shù)的。身邊有的同學(xué)會(huì)以為, Promise之所以狀態(tài)變?yōu)?fulfilled是因?yàn)閔ttp請(qǐng)求成功了。這便是封裝影響了你對(duì)Promise的真正認(rèn)識(shí),現(xiàn)在我來告訴大家,之所以請(qǐng)求成功Promise狀態(tài)會(huì)變?yōu)?code>fulfilled,是因?yàn)榉庋b Axios的人,讓它這樣的。假如我們自己來封裝,我們也完全可以讓http狀態(tài)碼為200時(shí)狀態(tài)變?yōu)槌晒Γ渌麪顟B(tài)我們都返回失敗,這樣我們就只能在http狀態(tài)為200時(shí)調(diào)用then中的函數(shù)了,其他狀態(tài)我們都將用catch拋出。
上面花較大篇幅來舉這個(gè)例子,只是為了告訴大家Promise的狀態(tài)只能由其包裹的異步操作的結(jié)果來決定,且什么樣的結(jié)果決定什么樣的狀態(tài)是自己的編程邏輯決定,而非固定,可以看出來Promise對(duì)象是一個(gè)很倔強(qiáng)的對(duì)象,這也是Promise對(duì)象名字的來歷,其英文單詞的意思為“承諾”。
Promise對(duì)象的狀態(tài)一旦改變,就將定性在此,不會(huì)發(fā)生二次改變,并且我們可以放心的在任何時(shí)候拿到這個(gè)結(jié)果并使用。這便是Promise相對(duì)于事件的優(yōu)勢(shì),假如我們?cè)谑录椒ㄖ性O(shè)定了回調(diào)函數(shù),只要是錯(cuò)過了這個(gè)事件,即使是再去監(jiān)聽也是得不到結(jié)果的,而Promise可以在任何時(shí)候拿到結(jié)果。
Promise的基本用法
首先,Promise的三種狀態(tài)的改變只有兩種情況
- 從
pending變?yōu)?code>fulfilled,這時(shí)我們稱為resolve - 從
pending變?yōu)?code>rejected,這時(shí)我們稱為reject
基礎(chǔ)較好的同學(xué),相信在看到我上面舉的例子的時(shí)候,已經(jīng)對(duì)Promise的基本用法有所了解。下面我們用代碼做一個(gè)最簡(jiǎn)單的演示
首先我們得知道,在ES6中,Promise是一個(gè)構(gòu)造函數(shù),用來生成Promise對(duì)象實(shí)例,就類似于Array、Function、Object這樣的構(gòu)造函數(shù)一樣,用來生成對(duì)應(yīng)的實(shí)例。
const myPromise = new Promise((resolve,reject) => {
/*這里寫我們的異步操作方法*/
if(/*異步操作成功*/) {
resolve(/*這里我們可以寫參數(shù)也可以不寫,
寫參數(shù)代表要傳出來的值,不寫就只代表一個(gè)成功狀態(tài),
這個(gè)要看情況決定寫與不寫*/)
} else {
reject(/*同樣,這里我們可以寫參數(shù)也可不寫參數(shù)*/)
}
}
)
以上就是Promise對(duì)象最簡(jiǎn)單的應(yīng)用。其中,我們可以發(fā)現(xiàn),構(gòu)造函數(shù)中接受兩個(gè)參數(shù),一個(gè)是resolve一個(gè)是reject,它們分別是兩個(gè)函數(shù),作用為將異步操作結(jié)果作為參數(shù)傳出去,resolve是Promise狀態(tài)由pending變?yōu)?code>fulfilled時(shí)調(diào)用,reject是Promise狀態(tài)由pending變?yōu)?code>rejected時(shí)調(diào)用,一般情況下,我們都是在異步操作成功后,將異步操作的結(jié)果作為resolve的參數(shù)傳遞出去,這樣,思路會(huì)比較順,即異步操作成功,Promise狀態(tài)變?yōu)橐淹瓿?,同理,異步操作失敗,我們將結(jié)果給reject傳遞出去。
通過上面的講解,我們知道了,Promise回將異步操作的結(jié)果傳遞出來,那么傳遞出來的結(jié)果到哪了呢?或者說,誰來接這個(gè)傳出來的結(jié)果呢?
then方法的使用
then可以用來指定resolve和reject狀態(tài)的回調(diào)函數(shù),也就是說,我們傳出來的值是用then來接的。具體使用如下:
//我們接上面創(chuàng)建的myPromise代碼塊
myPromise.then(success => {
/*對(duì)于resolve函數(shù)傳出值的處理*/
},error => {
/*對(duì)于reject函數(shù)傳出值的處理*/
})
通過上面的代碼我們可以看出來,then接受兩個(gè)回調(diào)函數(shù)做為參數(shù),分別代表resolve和reject狀態(tài)下的回調(diào),其中兩個(gè)函數(shù)的參數(shù)為Promise對(duì)象的傳出值。
說到這里,有的同學(xué)會(huì)提出疑問:“我們平時(shí)使用的then是只接受一個(gè)參數(shù)的呀,沒見過接受倆參數(shù)的”
這里我來解答,因?yàn)?code>then方法執(zhí)行完,是會(huì)再返回一個(gè)Promise對(duì)象的,而Promise對(duì)象的錯(cuò)誤是冒泡傳遞的,所以,我們可以不用對(duì)每一次的報(bào)錯(cuò)都做處理,我們可以放在Promise鏈最后用catch方法統(tǒng)一捕獲并處理(當(dāng)然,加入Promise對(duì)象不做拋出,我們是無法捕獲到這些信息的)。所以,then方法的第二個(gè)參數(shù)是可選的,并且,又因?yàn)?code>then返回的又是一個(gè)Promise對(duì)象,所以我們才可以使用鏈?zhǔn)綄懛?/strong>。
既然我們可以把錯(cuò)誤集中到一起處理,那么我們可不可以把成功集中處理呢?
Promise.all()方法的使用
通常我們會(huì)有這樣的需求,‘當(dāng)幾個(gè)異步操作全部完成,我們才做統(tǒng)一處理’,這就是我們使用all方法的時(shí)候,它的意思是,同時(shí)有幾個(gè)Promise對(duì)象正在執(zhí)行,我們可以集中在一起拿到各個(gè)Promise返回的值,當(dāng)然不止是成功時(shí)候的返回值,也可以包括失敗返回的值,需要注意的是,成功的時(shí)候返回的是一個(gè)結(jié)果數(shù)組,而失敗的時(shí)候,我們會(huì)捕獲第一個(gè)失敗的值。我們看下面的代碼
const promiseOne = new Promise(resolve => {
resolve('成功了')
})
const promiseTwo = new Promise((resolve, reject) => {
reject('失敗了')
})
const promiseThere = new Promise((resolve, reject) => {
resolve('哈哈')
})
Promise.all([promiseOne,promiseThere]).then(result => {
console.log(result )
}).catch(err => {
console.log(err)
})
這時(shí)控制的輸出結(jié)果為
const promiseOne = new Promise(resolve => {
resolve('成功了')
})
const promiseTwo = new Promise((resolve, reject) => {
reject('失敗了')
})
const promiseThere = new Promise((resolve, reject) => {
resolve('哈哈')
})
Promise.all([promiseOne,promiseTwo,promiseThere]).then(result => {
console.log(result )
}).catch(err => {
console.log(err)
})
這時(shí)控制臺(tái)輸出的結(jié)果為
上面的兩個(gè)代碼塊,帶大家演示了
Promise.all()方法的使用,細(xì)心的同學(xué)會(huì)發(fā)現(xiàn),在我第一次輸出結(jié)果的圖中嗎,控制臺(tái)除了打印結(jié)果外,還有一行紅字,‘Uncaught (in promise) 失敗了’,那么這個(gè)信息是來自哪里呢?這是來自我在創(chuàng)建“promiseTwo ”的時(shí)候,那么同樣的代碼,在第二個(gè)代碼塊怎么沒有報(bào)錯(cuò)呢?因?yàn)?,第二個(gè)代碼塊中,我用到了“promiseTwo”,并對(duì)拋出的錯(cuò)誤做了處理,第一個(gè)代碼塊我沒有用,所以也就沒法處理他的錯(cuò)誤,他就會(huì)自動(dòng)拋出他的錯(cuò)(因?yàn)槲以趧?chuàng)建他的時(shí)候用到了reject方法拋出這個(gè)錯(cuò))。我沒有用它它還拋出錯(cuò)誤了,這不是浪費(fèi)性能嗎?事實(shí)上,
Promise對(duì)象在創(chuàng)建的時(shí)候就會(huì)執(zhí)行(也就是上面提到的pending),所以我們?cè)陂_發(fā)中一般不這么創(chuàng)建,而是將Promise放到一個(gè)函數(shù)中。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
就像上面這樣。這里就是我們?cè)陂_發(fā)中需要注意的細(xì)節(jié)了,這樣創(chuàng)建的Promise對(duì)象才是最優(yōu)化的。
關(guān)于Promise對(duì)象,我們?cè)趯?shí)際開發(fā)中,我上面講的都是經(jīng)常用到的,當(dāng)然還會(huì)有一些不常用的關(guān)于PromiseAPI,這里需要用心的同學(xué)們?nèi)プ约簩Q辛耍疚恼逻€是以往拋轉(zhuǎn)引玉作用。想要了解更多的關(guān)于Promise知識(shí),愛心滿滿的作者總會(huì)給大家?guī)韨魉烷T
-
http://es6.ruanyifeng.com/#docs/promise
今天文章就到此結(jié)束了,相信認(rèn)真讀完文章的你會(huì)對(duì)Promise有一個(gè)更加清晰的認(rèn)識(shí),同時(shí)對(duì)于之前使用過的和Promise相關(guān)的“Axios”、“Fetch”等庫也會(huì)有更加清晰的認(rèn)識(shí)。
感謝您的瀏覽~