Promise 是什么?
Promise是ES6語法,是JS中解決異步編程的新解決方案。(舊的解決方案是單純的調(diào)用回調(diào)函數(shù))
從語法上來說,Promise是一個(gè)構(gòu)造函數(shù)
從功能上來說,Promise對(duì)象用來封裝一個(gè)異步操作,并可以獲取其成功或者失敗的結(jié)果值
異步編程包括但不限于以下幾個(gè):
- fs 文件操作
- 數(shù)據(jù)庫操作
- Ajax
- 定時(shí)器
為什么要用Promise?
Promise支持鏈?zhǔn)秸{(diào)用,解決回調(diào)地獄問題
在 Promise 出現(xiàn)以前,我們處理一個(gè)異步網(wǎng)絡(luò)請求,大概是這樣:
// 請求 代表 一個(gè)異步網(wǎng)絡(luò)調(diào)用。
// 請求結(jié)果 代表網(wǎng)絡(luò)請求的響應(yīng)。
請求1(function(請求結(jié)果1){
處理請求結(jié)果1
})
看起來還不錯(cuò)。
但是,需求變化了,我們需要根據(jù)第一個(gè)網(wǎng)絡(luò)請求的結(jié)果,再去執(zhí)行第二個(gè)網(wǎng)絡(luò)請求,代碼大概如下:
請求1(function(請求結(jié)果1){
請求2(function(請求結(jié)果2){
處理請求結(jié)果2
})
})
看起來也不復(fù)雜。
但是。。需求是永無止境的,于是乎出現(xiàn)了如下的代碼:
請求1(function(請求結(jié)果1){
請求2(function(請求結(jié)果2){
請求3(function(請求結(jié)果3){
請求4(function(請求結(jié)果4){
請求5(function(請求結(jié)果5){
請求6(function(請求結(jié)果3){
...
})
})
})
})
})
})
這就出現(xiàn)了所謂的回調(diào)地獄(回調(diào)函數(shù)嵌套調(diào)用,外部回調(diào)函數(shù)異步執(zhí)行的結(jié)果是嵌套的回調(diào)執(zhí)行的條件)
回調(diào)地獄帶來的負(fù)面作用有以下幾點(diǎn):
- 代碼臃腫。
- 可讀性差。
- 耦合度過高,可維護(hù)性差。
- 代碼復(fù)用性差。
- 容易滋生 bug。
- 只能在回調(diào)里處理異常。
解決方案: Promise鏈?zhǔn)秸{(diào)用
Promise初體驗(yàn)
// resolve 和 reject 都是函數(shù)類型的數(shù)據(jù)
const p = new Promise ((resolve, reject) => {
let status = true;
setTimeout(() => {
if(status) {
resolve('正確信息')
} else {
reject('錯(cuò)誤信息')
}
},2000)
})
p.then((data) => {
console.log(data);
})
Promise狀態(tài)改變
Promise 對(duì)象有三個(gè)狀態(tài),并且狀態(tài)一旦改變,便不能再被更改為其他狀態(tài)。
這個(gè)狀態(tài)實(shí)際上是Promise實(shí)例對(duì)象中的一個(gè)屬性[PromiseState]
實(shí)例對(duì)象中的另一個(gè)重要的屬性 PromiseResult,存的是對(duì)象成功或者失敗的結(jié)果
-
pending,異步任務(wù)正在進(jìn)行。(未決定的) -
resolved(也可以叫fulfilled),異步任務(wù)執(zhí)行成功。 -
rejected,異步任務(wù)執(zhí)行失敗。
Promise狀態(tài)改變只有 pending 變?yōu)?resolved 和 pending 變?yōu)?rejected 這兩種
且一個(gè) Promise 對(duì)象只能改變一次,無論成功或者失敗都會(huì)有一個(gè)結(jié)果數(shù)據(jù),成功的結(jié)果數(shù)據(jù)一般稱為 value,失敗的結(jié)果數(shù)據(jù)一般稱為 reason。
Promise的API
1. Promise構(gòu)造函數(shù)
- executor函數(shù):執(zhí)行器
- resolve函數(shù):內(nèi)部定義成功時(shí)我們執(zhí)行的函數(shù)
- reject函數(shù):內(nèi)部定義失敗時(shí)我們執(zhí)行的函數(shù)
說明:executor 會(huì)再 Promise 內(nèi)部立即同步調(diào)用,異步操作在執(zhí)行器中執(zhí)行。
舉例說明:
const p = new Promise((resolve, reject) => {
console.log('222')
})
console.log('111')
// 打印結(jié)果為: 111 222 , 從而證明了上面的結(jié)論
2. Promise.prototype.then()
語法:
p.then(onResolved[, onRejected]);
p.then(value => {
// fulfillment
}, reason => {
// rejection
});
指定用于得到成功 value 的成功回調(diào)和用于得到失敗 reason 的失敗回調(diào),返回一個(gè)新的 promise 對(duì)象。
3. Promise.prototype.catch()
catch() 方法返回一個(gè) Promise,并且處理拒絕的情況。它的行為與調(diào)用 Promise.prototype.then(undefined, onRejected) 相同。他只能用來指定失敗的回調(diào)
同樣支持鏈?zhǔn)秸{(diào)用,本質(zhì)上其實(shí)是then()方法的一個(gè)單獨(dú)封裝
語法:
p.catch(onRejected);
p.catch(function(reason) {
// 拒絕
});
3. Promise.resolve() 方法
這個(gè)方法比較特殊,它是屬于 Promise 這個(gè)函數(shù)對(duì)象的,并不屬于實(shí)例對(duì)象,也就是說它是靜態(tài)成員。
用法:接收一個(gè)參數(shù),返回一個(gè)promise成功或者失敗對(duì)象
語法:Promise.resolve(value);
// 如果傳入的參數(shù)為非 promise 類型的對(duì)象,則返回的結(jié)果為成功的promise對(duì)象
let p1 = Promise.resolve(123)
console.log(p1)
// 如果傳入的參數(shù)為 promise 對(duì)象,則參數(shù)的結(jié)果決定了 resolve 的結(jié)果,比如:
let p2 = Promise.resolve(new Promise((resolve, reject) => {
resolve('ok')
}))
console.log(p2) // 因?yàn)閰?shù)是promise對(duì)象,且返回的結(jié)果是成功的,值為ok,那么此時(shí)p2的結(jié)果也是成功的,值同樣為ok
4. Promise.reject() 方法
這個(gè)方法和上面的 Promise.resolve 方法一樣,是屬于 Promise 這個(gè)函數(shù)對(duì)象的,不屬于實(shí)例對(duì)象,同樣為靜態(tài)成員。
用法:快速返回一個(gè)帶有拒絕原因的promise對(duì)象
語法:Promise.reject(reason)
let p1 = Promise.reject(123)
let p2 = Promise.reject(new Promise((resolve, reject) => {
resolve('成功')
}))
console.log(p1)
console.log(p2)
// 無論你傳入?yún)?shù)是不是promise對(duì)象,都會(huì)返回一個(gè)被拒絕的promise對(duì)象,對(duì)調(diào)試和選擇性錯(cuò)誤捕捉很有幫助
Promise.all()
這個(gè)方法也是屬于 Promise 函數(shù)對(duì)象的,不屬于實(shí)例對(duì)象。
Promise.all() 方法接收一個(gè)promise的 iterable 類型(注:Array,Map,Set都屬于ES6的iterable類型)的輸入,并且只返回一個(gè)Promise實(shí)例, 那個(gè)輸入的所有promise的resolve回調(diào)的結(jié)果是一個(gè)數(shù)組。這個(gè)Promise的resolve回調(diào)執(zhí)行是在所有輸入的promise的resolve回調(diào)都結(jié)束,或者輸入的iterable里沒有promise了的時(shí)候。它的reject回調(diào)執(zhí)行是,只要任何一個(gè)輸入的promise的reject回調(diào)執(zhí)行或者輸入不合法的promise就會(huì)立即拋出錯(cuò)誤,并且reject的是第一個(gè)拋出的錯(cuò)誤信息。
語法:Promise.all(iterable)
iterable: 一般為包含 n 個(gè) promise 的數(shù)組
總結(jié):返回一個(gè)新的 promise ,只有所有的 promise 都成功才成功,只要有一個(gè)失敗了就直接失敗
let p1 = new Promise((resolve, reject) => {
resolve('p1')
})
let p2 = new Promise((resolve, reject) => {
resolve('p2')
})
let p3 = new Promise((resolve, reject) => {
resolve('p3')
})
const result = Promise.all([p1, p2, p3])
console.log(result) // 返回值為三個(gè)promise對(duì)象成功的結(jié)果組成的數(shù)組
// 如果有一個(gè)失敗,返回值為失敗,失敗的結(jié)構(gòu)值就為第一個(gè)失敗的那個(gè)結(jié)果值
Promise.race()
這個(gè)方法也是屬于 Promise 函數(shù)對(duì)象的,不屬于實(shí)例對(duì)象。
語法:Promise.race(iterable);
iterable: 包含 n 個(gè) promise 的數(shù)組
總結(jié):返回一個(gè)新的 promise,第一個(gè)完成的promise的結(jié)果狀態(tài)就是最終的結(jié)果狀態(tài)
就相當(dāng)于在傳入的promise對(duì)象在賽跑,誰先改變狀態(tài),誰就決定race方法的返回結(jié)果,無論是成功或者失敗。
const p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
const result = Promise.race([p1, p2]).then((value) => {
console.log(value);
// 都成功,但是顯然p2更快,所以result的結(jié)果為two
});
Promise中的幾個(gè)關(guān)鍵問題
1. 如何改變promise對(duì)象的狀態(tài)
let p = new Promise((resolve, reject) => {
// 1.resolve 函數(shù)
resolve('ok') // pending => resolved
// 2. reject 函數(shù)
reject('error') // pending => rejected
// 3. 拋出錯(cuò)誤
throw '出問題了'
// 這三種方法都可以改變promise 的狀態(tài)
})
2. 能否執(zhí)行多個(gè)回調(diào)
promise指定多個(gè)成功或失敗的回調(diào)函數(shù),都會(huì)調(diào)用嗎?
當(dāng)promise改變?yōu)閷?duì)應(yīng)狀態(tài)時(shí)都會(huì)調(diào)用。
let p = new Promise((resolve, reject) => {
resolve('ok')
})
// 指定回調(diào) - 1
p.then(value => {
console.log(value)
})
// 指定回調(diào) - 2
p.then(value => {
alert(value)
})
// 很顯然只要時(shí)promise的狀態(tài)改變了,上面的兩個(gè)回調(diào)函數(shù)都會(huì)執(zhí)行
3. 改變狀態(tài)與執(zhí)行回調(diào)順序問題
改變promise狀態(tài)和執(zhí)行回調(diào)函數(shù)誰先誰后?
const p = new Promise((resolve, reject) => {
// 1. 當(dāng)執(zhí)行器中是一個(gè)同步任務(wù)的時(shí)候,那么會(huì)先改變狀態(tài)再執(zhí)行下面的then回調(diào)函數(shù)
resolve('ok')
// 2. 當(dāng)執(zhí)行器中時(shí)一個(gè)異步任務(wù)的時(shí)候,那么會(huì)先執(zhí)行下面的then回調(diào)函數(shù)再改變狀態(tài)
setTimeout(() => {
resolve('ok')
}, 1000)
})
p.then((value) => {
console.log(value)
}, (reason) => {
console.log(reason)
})
- 對(duì)于執(zhí)行器中時(shí)一個(gè)異步任務(wù)的時(shí)候,我們先執(zhí)行調(diào)用回調(diào)函數(shù),但是要等到異步任務(wù)中resolve的狀態(tài)改變之后,才會(huì)去執(zhí)行回調(diào)函數(shù)里面的代碼,然后對(duì)成功或失敗的結(jié)果做處理
4. 中斷promise鏈的方法
const p = new Promise((resolve, reject) => {
resolve(123)
})
p.then(value => {
console.log(111)
// 中斷promise鏈的唯一方法是返回一個(gè)pending狀態(tài)的promise對(duì)象
// 因?yàn)槿绻祷氐氖莗ending,那么then方法返回的也是一個(gè)pending狀態(tài)的promise對(duì)象
// 那么后續(xù)的then方法都不能執(zhí)行,因?yàn)闋顟B(tài)沒有改變
return new Promise(() => {})
// 這時(shí)候就只會(huì)打印111
}).then(value => {
console.log(222)
}).then(value => {
console.log(333)
})