含義
所謂Promise
,簡(jiǎn)單來(lái)說(shuō)就是一個(gè)容器,里面保存著某個(gè)未來(lái)才會(huì)結(jié)束的事情(通常是一個(gè)異步操作)的結(jié)果.從語(yǔ)法上來(lái)說(shuō),Promise
是一個(gè)對(duì)象,從它可以獲取異步操作的消息.
Promise
有以下兩個(gè)特點(diǎn):
- 對(duì)象的狀態(tài)不受外界影響.
Promise
對(duì)象代表一個(gè)異步操作,有三種狀態(tài):pending(進(jìn)行中)
,fulfilled(已成功)
和rejectd(已失敗)
.只有異步操作的結(jié)果,可以決定當(dāng)前是哪一種狀態(tài) - 一旦狀態(tài)改變,就不會(huì)再變(resolved已定型),任何時(shí)候都可以得到這個(gè)結(jié)果.如果改變已經(jīng)發(fā)生了,你再對(duì)
Promise
對(duì)象添加回調(diào)函數(shù),也會(huì)立即得到這個(gè)結(jié)果
缺點(diǎn):無(wú)法取消Promise
,一旦新建就會(huì)立即執(zhí)行;如果不設(shè)置回調(diào)函數(shù),Promise
內(nèi)部拋出的錯(cuò)誤,不會(huì)反映到外部;當(dāng)處于pending
狀態(tài)時(shí),無(wú)法得知進(jìn)展到哪一個(gè)階段
基本用法
Promise
對(duì)象是一個(gè)構(gòu)造函數(shù),用來(lái)生成Promise
實(shí)例
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise
構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù),該函數(shù)的兩個(gè)參數(shù)分別是resolve
和reject
.他們是兩個(gè)函數(shù),由JavaScript引擎提供,不用自己部署.
Promise
實(shí)例生成之后,可以用then
方法分別指定resolved
狀態(tài)和rejected
狀態(tài)的回調(diào)函數(shù)
promise.then(function(value) {
// success
}, function(error) {
// failure
});
這兩個(gè)函數(shù)都接受Promise
對(duì)象傳出的值作為參數(shù).
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
立即resolved的Promise是在本輪事件循環(huán)的末尾執(zhí)行,總是晚于本輪循環(huán)的同步任務(wù)
一般來(lái)說(shuō),調(diào)用resolve
或reject
以后,Promise的使命就完成了,后面操作應(yīng)該放到then
方法里面.所以在它們前面加上return
語(yǔ)句,這樣就不會(huì)發(fā)生意外.
new Promise((resolve, reject) => {
return resolve(1);
// 后面的語(yǔ)句不會(huì)執(zhí)行
console.log(2);
})
Promise.prototype.then
作用是為Promise實(shí)例添加狀態(tài)改變時(shí)的回調(diào)函數(shù).返回的是一個(gè)新的Promise
實(shí)例.因此可以采用鏈?zhǔn)綄?xiě)法,即then
方法后面再調(diào)用另一個(gè)then
方法
采用鏈?zhǔn)降?code>then,可以指定一組按照次序調(diào)用的回調(diào)函數(shù).這時(shí),前一個(gè)回調(diào)函數(shù)有可能返回的還是一個(gè)Promise
對(duì)象(有異步操作),這時(shí)后一個(gè)回調(diào)函數(shù)就會(huì)等待該Promise
對(duì)象的狀態(tài)發(fā)生變化,才會(huì)被調(diào)用
getJSON("/post/1.json").then(
post => getJSON(post.commentURL)
).then(
comments => console.log("resolved: ", comments),
err => console.log("rejected: ", err)
);
Promise.prototype.catch
Promise.prototype.catch
方法是.then(null, rejection)
的別名,用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// 等同于
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));
如果Promise狀態(tài)已經(jīng)變成resolved
,再拋出錯(cuò)誤是無(wú)效的
Promise對(duì)象的錯(cuò)誤具有"冒泡"性質(zhì),會(huì)一直向后傳遞.
一般來(lái)說(shuō),不要在then
方法里面定義Reject狀態(tài)的回調(diào)函數(shù)(即then
的第二個(gè)參數(shù)),總是使用catch
方法.理由是第二種寫(xiě)法可以捕獲前面then
方法執(zhí)行中的錯(cuò)誤,也更接近同步的寫(xiě)法(try/catch
).
Promise的錯(cuò)誤不會(huì)影響到Promise外部的代碼,天通俗的說(shuō)法是"Promise會(huì)吃掉錯(cuò)誤"
一般總是建議Promise對(duì)象后面要跟catch
方法,這樣可以處理Promise內(nèi)部發(fā)生的錯(cuò)誤.catch
方法返回的還是一個(gè)Promise對(duì)象,因此后面還可以接著調(diào)用then
方法
catch
方法之中還可以?huà)伋鲥e(cuò)誤,而且可以繼續(xù)使用catch
方法捕獲
Promise.prototype.finally
finally
方法用于指定不管Promise對(duì)象狀態(tài)如果,都會(huì)執(zhí)行的操作.該方法是ES7引入標(biāo)準(zhǔn)的
finally
方法的回調(diào)函數(shù)不接受任何參數(shù),finally
方法里面的操作,應(yīng)該是與狀態(tài)無(wú)關(guān)的,不依賴(lài)與Promise的執(zhí)行結(jié)果
實(shí)現(xiàn)finally
代碼:
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
Promise.all
用于將多個(gè)Promise實(shí)例,包裝成一個(gè)新的Promise實(shí)例
const p = Promise.all([p1, p2, p3]);
Promise.all
方法接受一個(gè)數(shù)組作為參數(shù),p1,p2,p3都是Promise實(shí)例.如果不是則調(diào)用Promise.resolve
方法將參數(shù)轉(zhuǎn)為Promise實(shí)例,再進(jìn)一步處理.(Promise.all
方法的參數(shù)可以不是數(shù)組,但必須具有Iterator接口,且返回的每個(gè)成員都是Promise實(shí)例)
p
的狀態(tài)由p1,p2,p3決定:
- 如果三者狀態(tài)都變成
fulfilled
,p的狀態(tài)才會(huì)變成fulfilled
,此時(shí)三者的返回值組成一個(gè)數(shù)組,傳遞給p的回調(diào)函數(shù) - 只有三者中有一個(gè)被
reject
,p的狀態(tài)就變成rejected
,此時(shí)第一個(gè)被reject
的實(shí)例的返回值,會(huì)傳遞給p的回調(diào)函數(shù)
注意,如果作為參數(shù)的Promise實(shí)例,自己定義了catch
方法,那么它一旦被rejected
,并不會(huì)觸發(fā)Promise.all
的catch
方法
Promise.race
該方法同樣是將多個(gè)Promise實(shí)例包裝成一個(gè)新的Promise實(shí)例.
const p = Promise.race([p1, p2, p3]);
只要參數(shù)中三個(gè)Promise實(shí)例率先改變狀態(tài),p的狀態(tài)就跟著改變.那個(gè)率先改變的Promise實(shí)例的返回值,就傳遞給p的回調(diào)函數(shù)
Promise.resolve
該方法可以將現(xiàn)有對(duì)象轉(zhuǎn)為Promise對(duì)象
Promise.resolve('foo')
// 等價(jià)于
new Promise(resolve => resolve('foo'))
Promise.resolve
方法的參數(shù)分成四種情況:
- 參數(shù)是一個(gè)Promise實(shí)例
- 參數(shù)是一個(gè)
thenable
對(duì)象
thenable
對(duì)象指得是具有then
方法的對(duì)象.會(huì)將這個(gè)對(duì)象轉(zhuǎn)為Promise對(duì)象,然后就立即執(zhí)行thenable
對(duì)象的then
方法 - 參數(shù)不是具有
then
方法的對(duì)象,或根本不是對(duì)象
返回一個(gè)新的Promise對(duì)象,狀態(tài)為resolved
.所以回調(diào)函數(shù)會(huì)立即執(zhí)行 - 不帶有任何參數(shù)
Promise.resolve
方法允許調(diào)用時(shí)不帶任何參數(shù),直接返回一個(gè)resolved
狀態(tài)的Promise對(duì)象
需要注意的是,立即resolve
的Promise對(duì)象,是在本輪"事件循環(huán)"的結(jié)束時(shí),而不是在下一輪的開(kāi)始時(shí)
Promise.reject
該方法也會(huì)返回一個(gè)新的Promise實(shí)例,該實(shí)例的狀態(tài)為rejected
注意:Promise.reject
方法的參數(shù),會(huì)原封不動(dòng)的座位reject
的理由,變成后續(xù)方法的參數(shù)
應(yīng)用
- 加載圖片
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};
- Generator函數(shù)和Promise的結(jié)合
使用Generator函數(shù)管理流程,遇到異步操作的時(shí)候,通常返回一個(gè)Promise對(duì)象
function getFoo () {
return new Promise(function (resolve, reject){
resolve('foo');
});
}
const g = function* () {
try {
const foo = yield getFoo();
console.log(foo);
} catch (e) {
console.log(e);
}
};
function run (generator) {
const it = generator();
function go(result) {
if (result.done) return result.value;
return result.value.then(function (value) {
return go(it.next(value));
}, function (error) {
return go(it.throw(error));
});
}
go(it.next());
}
run(g);
Promise.try
實(shí)際開(kāi)發(fā)中,經(jīng)常會(huì)遇到一種情況:不知道或者不想?yún)^(qū)分,一個(gè)函數(shù)是同步函數(shù)還是異步操作,但是享用Promise來(lái)處理
- 使用
async
函數(shù)
const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next
- 使用
new Promise
const f = () => console.log('now');
(
() => new Promise(
resolve => resolve(f())
)
)();
console.log('next');
// now
// next
鑒于該需求很常見(jiàn),所以現(xiàn)在提案,提供Promise.try
方法替代上面的寫(xiě)法
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
由于Promise.try
為所有操作提供了統(tǒng)一的處理機(jī)制,所以如果想用then
方法管理流程,最好都用Promise.try
包裝一下.這樣很多好處,其中一點(diǎn)就是處理異常
Promise.try(database.users.get({id: userId}))
.then(...)
.catch(...)
事實(shí)上,Promise.try
就是模擬try
代碼塊,就像Promise.catch
moni的是catch
代碼塊