一、什么是Promise?
Promise是js異步編程的解決方案。從語(yǔ)法上看,Promise是一個(gè)構(gòu)造函數(shù);從功能上看,promise對(duì)象用來(lái)封裝一個(gè)異步操作并可以獲取其結(jié)果;
每個(gè)Promise實(shí)例對(duì)象都有三個(gè)狀態(tài),分別為pending(初始,狀態(tài)未改變)、fulfilled(成功)、rejected(失?。?
promise對(duì)象的狀態(tài)只能從pending到fulfilled或從pending到rejected變化,并且狀態(tài)改變后不再改變;
二、為什么使用Promise?
Promise指定回調(diào)函數(shù)的方式或時(shí)機(jī)更靈活
在傳統(tǒng)回調(diào)函數(shù)中,必須在異步任務(wù)啟動(dòng)前指定回調(diào)函數(shù);
而在使用Promise時(shí),我們通常先啟動(dòng)異步任務(wù),然后返回一個(gè)promise對(duì)象,最后通過(guò)then/catch指定回調(diào)函數(shù)(甚至可以在異步任務(wù)結(jié)束后指定回調(diào)函數(shù));
/* 傳統(tǒng)回調(diào)函數(shù) */
$.ajax({
url: '/user/info',
method: 'post',
success: function() {
// 請(qǐng)求成功回調(diào),需要在在開(kāi)啟請(qǐng)求之前指定
}
})
/* promise指定回調(diào) */
// 1. 開(kāi)啟異步任務(wù),返回一個(gè)promise對(duì)象
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 200);
})
// 2. 指定回調(diào)函數(shù)
promise.then(
value => {console.log('resolve', value)},
reason => {console.log('reject', reason)}
)
// 3.異步任務(wù)結(jié)束后指定回調(diào)函數(shù)
setTimeout(() => {
promise.then(
value => {console.log('resolve', value)},
reason => {console.log('reject', reason)}
)
}, 2000)
Promise支持鏈?zhǔn)秸{(diào)用,可以解決回調(diào)地獄問(wèn)題
什么是回調(diào)地獄? 回調(diào)函數(shù)嵌套調(diào)用,上一個(gè)回調(diào)函數(shù)返回的結(jié)果是嵌套回調(diào)函數(shù)執(zhí)行的條件;
回調(diào)地獄的問(wèn)題? 不便于開(kāi)發(fā)者閱讀,不便于異常排查;
解決方案?Promise鏈?zhǔn)秸{(diào)用;
js異步任務(wù)的終極解決方案? async/await;
三、理解Promise的一些關(guān)鍵性問(wèn)題
如何改變promies的狀態(tài)?
上面已經(jīng)提到了promise的狀態(tài)有三種,pending、fulfilled、rejected;
一般說(shuō)到改變狀態(tài)我們都會(huì)想到resolve()和reject()這兩個(gè)方法,其實(shí)還有一種改變狀態(tài)的方式,這里總結(jié)一下:
- 使用resolve() 狀態(tài)由pending變?yōu)閒ulfilled;
- 使用reject() 狀態(tài)由pending變?yōu)閞ejected;
- throw Error('error')/ throw 任意值 狀態(tài)由pending;
一個(gè)promise實(shí)例指定多個(gè)成功/失敗的回調(diào),都會(huì)被調(diào)用嗎?
當(dāng)promise狀態(tài)改變時(shí),對(duì)應(yīng)的回調(diào)函數(shù)都會(huì)被調(diào)用;
const promise = new Promise((resolve, reject) => {
resolve(1) // 觸發(fā)所有成功狀態(tài)的回調(diào)函數(shù)
})
// 指定第一個(gè)回調(diào)
promise.then(
value => {console.log(value)}, // 輸出1
reason => {console.log(reason)}
)
// 指定第二個(gè)回調(diào)
promise.then(
value => {console.log(value)}, // 輸出1
reason => { console.log(reason) }
)
改變promise狀態(tài)和指定回調(diào)函數(shù)誰(shuí)先誰(shuí)后?
都有可能,正常情況下先指定回調(diào)函數(shù)再改變狀態(tài),但也可以先改變狀態(tài)再指定回調(diào)函數(shù);
如何先改變狀態(tài)再指定回調(diào)函數(shù)?
- 在執(zhí)行器中同步調(diào)用resolve()/reject();
- 延遲更長(zhǎng)的時(shí)間調(diào)用then,例如使用定時(shí)器;
什么時(shí)候才能得到數(shù)據(jù)?當(dāng)調(diào)用回調(diào)函數(shù)時(shí),都可以在回調(diào)函數(shù)中得到數(shù)據(jù);
promise.then()返回的新promise對(duì)象的狀態(tài)由誰(shuí)決定?
總結(jié):由then指定的回調(diào)函數(shù)(成功/失敗)的結(jié)果決定;
具體:
- 如果拋出異常,新promise對(duì)象的狀態(tài)變?yōu)閞ejected,結(jié)果是拋出的異常
- 如果返回的是非promise的任意值,新promise對(duì)象的狀態(tài)變?yōu)閒ulfilled,結(jié)果是當(dāng)前任意值;
- 如果返回另一個(gè)promise對(duì)象,此promise對(duì)象的狀態(tài)及結(jié)果就會(huì)成為新promise的狀態(tài)和結(jié)果;
new Promise((resolve, reject) => {
resolve(1) // 輸出順序:1 => 3 => 5
// reject(1) // 輸出順序:2 => 4 => 5
}).then(
value => {
console.log('onResolve1', value) // 順序1
return Promise.resolve(value + 2) // 返回一個(gè)新的promise,且狀態(tài)為fulfilled
},
reason => {
console.log('onReject1', reason) // 順序2
throw 3 // 拋出異常
}
).then(
value => { console.log('onResolve2', value) // 順序3 },
reason => { console.log('onReject2', reason) // 順序4 }
).then(
value => { console.log('onResolve3', value) // 順序5 },
reason => { console.log('onReject3', reason) // 順序6 }
)
Promise怎么串聯(lián)多個(gè)操作任務(wù)?
promise.then()返回一個(gè)新的promise對(duì)象,通過(guò)then的鏈?zhǔn)秸{(diào)用,可以串聯(lián)多個(gè)同步/異步任務(wù);
其中串聯(lián)異步任務(wù)需要new一個(gè)promise對(duì)象;
new Promise((resolve, reject) => {
setTimeout(() => {
console.log('執(zhí)行任務(wù)1(異步)')
resolve(1)
}, 1000);
}).then(value => {
console.log('任務(wù)1的結(jié)果:', value)
console.log('執(zhí)行任務(wù)2(同步)')
return 2
}).then(value => {
console.log('任務(wù)2的結(jié)果: ', value)
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('執(zhí)行任務(wù)3(異步)')
resolve(3)
}, 2000);
})
}).then(value => {
console.log('任務(wù)3的結(jié)果', value)
})
Promise的異常傳透怎么實(shí)現(xiàn)的?
當(dāng)我們使用then的鏈?zhǔn)秸{(diào)用時(shí),通常會(huì)在調(diào)用鏈的最后指定失敗的回調(diào)catch;
當(dāng)catch前面的then中有一環(huán)出現(xiàn)了異?;蛘郀顟B(tài)變?yōu)閞ejected,都會(huì)通過(guò)then依次傳遞到catch中,注意并不是直接跳到catch;
因?yàn)槲覀兘y(tǒng)一用catch做異常處理了,所以一般我們不在then中寫(xiě)rejected回調(diào),這時(shí)rejected的回調(diào)默認(rèn)為 reason => { throw reason };
new Promise((resolve, reject) => {
// resolve(1)
reject(1)
}).then(
value => {
console.log('onResolve1', value)
return 2
},
reason => { throw reason } // 失敗回調(diào)的默認(rèn)值, 將異常傳透給catch
).then(
value => { console.log('onResolve2', value) }
).catch(
reason => { console.log('onReject', reason) }
)
怎么中斷promise鏈?
在回調(diào)函數(shù)中返回一個(gè)狀態(tài)為pending的promise,可以中斷promise鏈;
new Promise((resolve, reject) => {
// resolve(1)
reject(1)
}).then(
value => { console.log('onResolve1', value) }
).catch(reason => {
console.log('onReject', reason)
return new Promise(() => {}) // 返回一個(gè)pending狀態(tài)的promise,中止promise鏈
}).then(
value => { console.log('onResolve3', value) }
)
四、源碼實(shí)現(xiàn)
(function() {
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
/*
Promise類(lèi)
excutor:執(zhí)行器函數(shù)(同步)
*/
class Promise {
constructor(excutor) {
const _this = this
this.status = PENDING // promise實(shí)例對(duì)象的狀態(tài),初始值為pending
this.data = undefined // 存儲(chǔ)結(jié)果數(shù)據(jù)的屬性
this.callbacks = [] // 回調(diào)函數(shù),每個(gè)元素的數(shù)據(jù)解構(gòu): { onResolved() {}, onRejected() {} }
// 觸發(fā)成功回調(diào)
function resolve(value) {
// 當(dāng)前狀態(tài)不是pending,結(jié)束
if (_this.status !== PENDING) return false
// 修改當(dāng)前狀態(tài)為fulfilled
_this.status = FULFILLED
// 保存數(shù)據(jù)結(jié)果
_this.data = value
// 如果由待執(zhí)行的回調(diào)函數(shù),立即異步執(zhí)行回調(diào)函數(shù)的onResolved
if (_this.callbacks.length) {
setTimeout(() => {
_this.callbacks.forEach(callbackObj => {
callbackObj.onResolved(value)
})
})
}
}
// 觸發(fā)失敗回調(diào)
function reject(reason) {
// 當(dāng)前狀態(tài)不是pending,結(jié)束
if (_this.status !== PENDING) return false
// 修改當(dāng)前狀態(tài)為rejected
_this.status = REJECTED
// 保存數(shù)據(jù)結(jié)果
_this.data = reason
// 如果有待執(zhí)行的回調(diào)函數(shù),立即異步執(zhí)行回調(diào)函數(shù)的onRejected
if (_this.callbacks.length) {
setTimeout(() => {
_this.callbacks.forEach(callbackObj => {
callbackObj.onRejected(reason)
})
})
}
}
// 處理執(zhí)行器異常
try {
excutor(resolve, reject)
} catch (error) {
reject(error)
}
}
/*
then方法
指定成功和失敗的回調(diào)
返回一個(gè)promise
*/
then(onResolved, onRejected) {
const _this = this
onResolved = typeof onResolved === 'function' ? onResolved : value => value // 向下傳遞value
// 指定默認(rèn)的失敗回調(diào)onRejected(實(shí)現(xiàn)異常傳透的關(guān)鍵)
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
return new Promise((resolve, reject) => {
// 調(diào)用指定回調(diào)函數(shù)處理,根據(jù)執(zhí)行結(jié)果,改變r(jià)eturn的promise的狀態(tài)
function handle(callback) {
/*
1. 如果拋出異常,return的Promise的結(jié)果就是失敗的,reason就是error
2. 如果返回的結(jié)果不是promise類(lèi)型,return的Promise的結(jié)果就是成功的,value就是該返回的結(jié)果
3. 如果是promise,return的Promise的結(jié)果就是該promise的結(jié)果
*/
try {
const result = callback(_this.data)
if (result instanceof Promise) {
// 3. 返回的結(jié)果是promise,return的Promise的結(jié)果就是該promise的結(jié)果
result.then(
value => resolve(value),
reason => reject(reason)
)
// result.then(resolve, reject)
} else {
// 2. 返回的結(jié)果不是promise類(lèi)型,return的Promise的結(jié)果就是成功的,value就是該返回的結(jié)果
resolve(result)
}
} catch (error) {
// 1. 拋出異常,return的Promise的結(jié)果就是失敗的,reason就是error
reject(error)
}
}
// 狀態(tài)還沒(méi)改變,先將回調(diào)函數(shù)存儲(chǔ)起來(lái),用于狀態(tài)改變時(shí)調(diào)用
if (this.status === PENDING) {
this.callbacks.push({
onResolved () {
handle(onResolved)
},
onRejected () {
handle(onRejected)
}
})
} else if (this.status === FULFILLED) {
// 狀態(tài)是fulfilled,異步執(zhí)行成功回調(diào)
setTimeout(() => {
handle(onResolved)
})
} else {
// 狀態(tài)是rejected,異步執(zhí)行失敗回調(diào)
setTimeout(() => {
handle(onRejected)
})
}
})
}
/*
catch方法
指定失敗的回調(diào)
返回一個(gè)promise
*/
catch(onRejected) {
return this.then(undefined, onRejected)
}
/*
Promise的靜態(tài)方法resolve
返回一個(gè)指定value的成功/失敗的promise
*/
static resolve = function(value) {
return new Promise((resolve, reject) => {
// value是一個(gè)promise
if (value instanceof Promise) {
value.then(resolve, reject)
} else { // value不是一個(gè)promise
resolve(value)
}
})
}
/*
Promise的靜態(tài)方法reject
返回一個(gè)指定reason的失敗的promise
*/
static reject = function(reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
/*
Promise的靜態(tài)方法all
返回一個(gè)promise,當(dāng)多有promise都成功時(shí)才成功,有一個(gè)失敗則失敗
*/
static all = function(promises) {
// 定義一個(gè)指定長(zhǎng)度的數(shù)組,用于存儲(chǔ)resolve的值
const values = new Array(promises.length)
// 計(jì)數(shù),用于記錄成功的promise次數(shù)
let resolveCount = 0
return new Promise((resolve, reject) => {
promises.forEach((p, index) => {
Promise.resolve(p).then(
value => {
values[index] = value
resolveCount ++
// 如果全部都成功了,將返回的promise改為成功
if (resolveCount === promises.length) {
resolve(values)
}
},
// 有一個(gè)promise失敗,將返回的promise改為失敗
reason => {
reject(reason)
}
)
})
})
}
/*
Promise的靜態(tài)方法race
返回一個(gè)promise,其結(jié)果由第一 個(gè)完成的promise決定
*/
static race = function(promises) {
return new Promise((resolve, reject) => {
promises.forEach(p => {
Promise.resolve(p).then(
// 有一個(gè)promise成功,將返回的promise改為成功
value => {
resolve(value)
},
// 有一個(gè)promise失敗,將返回的promise改為失敗
reason => {
reject(reason)
}
)
})
})
}
}
// 暴露Promise
window.Promise = Promise
})()