1.Promise 的含義
Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大。它由社區(qū)最早提出和實(shí)現(xiàn),ES6 將其寫(xiě)進(jìn)了語(yǔ)言標(biāo)準(zhǔn),統(tǒng)一了用法,原生提供了Promise對(duì)象。
所謂Promise,簡(jiǎn)單說(shuō)就是一個(gè)容器,里面保存著某個(gè)未來(lái)才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果。從語(yǔ)法上說(shuō),Promise 是一個(gè)對(duì)象,從它可以獲取異步操作的消息。
Promise對(duì)象有以下兩個(gè)特點(diǎn)。
1)對(duì)象的狀態(tài)不受外界影響。Promise對(duì)象代表一個(gè)異步操作,有三種狀態(tài):pending(進(jìn)行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操作的結(jié)果,可以決定當(dāng)前是哪一種狀態(tài),任何其他操作都無(wú)法改變這個(gè)狀態(tài)。這也是Promise這個(gè)名字的由來(lái),它的英語(yǔ)意思就是“承諾”,表示其他手段無(wú)法改變。
(2)一旦狀態(tài)改變,就不會(huì)再變,任何時(shí)候都可以得到這個(gè)結(jié)果。Promise對(duì)象的狀態(tài)改變,只有兩種可能:從pending變?yōu)?code>fulfilled和從pending變?yōu)?code>rejected。只要這兩種情況發(fā)生,狀態(tài)就凝固了,不會(huì)再變了,會(huì)一直保持這個(gè)結(jié)果,這時(shí)就稱(chēng)為resolved`(已定型)。
注意,為了行文方便,本章后面的·resolved·統(tǒng)一只指fulfilled狀態(tài),不包含rejected狀態(tài)。
有了Promise對(duì)象,就可以將異步操作以同步操作的流程表達(dá)出來(lái),避免了層層嵌套的回調(diào)函數(shù)。此外,Promise對(duì)象提供統(tǒng)一的接口,使得控制異步操作更加容易。
2、基本用法
ES6 規(guī)定,``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 引擎提供,不用自己部署。
resolve函數(shù)的作用是,將Promise對(duì)象的狀態(tài)從“未完成”變?yōu)椤俺晒Α保磸?pending 變?yōu)?code>resolved),在異步操作成功時(shí)調(diào)用,并將異步操作的結(jié)果,作為參數(shù)傳遞出去;reject函數(shù)的作用是,將Promise對(duì)象的狀態(tài)從“未完成”變?yōu)椤笆 保磸?pending 變?yōu)?rejected),在異步操作失敗時(shí)調(diào)用,并將異步操作報(bào)出的錯(cuò)誤,作為參數(shù)傳遞出去。
Promise實(shí)例生成以后,可以用then方法分別指定resolved狀態(tài)和rejected狀態(tài)的回調(diào)函數(shù)。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then方法可以接受兩個(gè)回調(diào)函數(shù)作為參數(shù)。第一個(gè)回調(diào)函數(shù)是Promise對(duì)象的狀態(tài)變?yōu)?code>resolved時(shí)調(diào)用,第二個(gè)回調(diào)函數(shù)是Promise對(duì)象的狀態(tài)變?yōu)?code>rejected時(shí)調(diào)用。其中,第二個(gè)函數(shù)是可選的,不一定要提供。這兩個(gè)函數(shù)都接受Promise對(duì)象傳出的值作為參數(shù)。
3.Promise.prototype.then()
Promise實(shí)例具有then方法,也就是說(shuō),then方法是定義在原型對(duì)象Promise.prototype上的。它的作用是為 Promise實(shí)例添加狀態(tài)改變時(shí)的回調(diào)函數(shù)。前面說(shuō)過(guò),then方法的第一個(gè)參數(shù)是resolved狀態(tài)的回調(diào)函數(shù),第二個(gè)參數(shù)(可選)是rejected狀態(tài)的回調(diào)函數(shù)。
then方法返回的是一個(gè)新的Promise實(shí)例(注意,不是原來(lái)那個(gè)Promise實(shí)例)。因此可以采用鏈?zhǔn)綄?xiě)法,即then方法后面再調(diào)用另一個(gè)then`方法。
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
采用鏈?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(function(post) {
return getJSON(post.commentURL);
}).then(function funcA(comments) {
console.log("resolved: ", comments);
}, function funcB(err){
console.log("rejected: ", err);
});
上面代碼中,第一個(gè)then方法指定的回調(diào)函數(shù),返回的是另一個(gè)Promise對(duì)象。這時(shí),第二個(gè)then方法指定的回調(diào)函數(shù),就會(huì)等待這個(gè)新的Promise對(duì)象狀態(tài)發(fā)生變化。如果變?yōu)閞esolved,就調(diào)用funcA,如果狀態(tài)變?yōu)?/code>rejected,就調(diào)用funcB`。
4.Promise.prototype.catch()
Promise.prototype.catch方法是.then(null, rejection)的別名,用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)。
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 處理 getJSON 和 前一個(gè)回調(diào)函數(shù)運(yùn)行時(shí)發(fā)生的錯(cuò)誤
console.log('發(fā)生錯(cuò)誤!', error);
});
上面代碼中,getJSON方法返回一個(gè) Promise對(duì)象,如果該對(duì)象狀態(tài)變?yōu)?code>resolved,則會(huì)調(diào)用then方法指定的回調(diào)函數(shù);如果異步操作拋出錯(cuò)誤,狀態(tài)就會(huì)變?yōu)?code>rejected,就會(huì)調(diào)用catch方法指定的回調(diào)函數(shù),處理這個(gè)錯(cuò)誤。另外,then方法指定的回調(diào)函數(shù),如果運(yùn)行中拋出錯(cuò)誤,也會(huì)被catch方法捕獲。
5.Promise.prototype.finally()
finally方法用于指定不管 Promise 對(duì)象最后狀態(tài)如何,都會(huì)執(zhí)行的操作。該方法是 ES2018 引入標(biāo)準(zhǔn)的
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
上面代碼中,不管promise最后的狀態(tài),在執(zhí)行完then或catch指定的回調(diào)函數(shù)以后,都會(huì)執(zhí)行finally方法指定的回調(diào)函數(shù)。
下面是一個(gè)例子,服務(wù)器使用 Promise 處理請(qǐng)求,然后使用finally方法關(guān)掉服務(wù)器。
server.listen(port)
.then(function () {
// ...
})
.finally(server.stop);
finally方法的回調(diào)函數(shù)不接受任何參數(shù),這意味著沒(méi)有辦法知道,前面的 Promise 狀態(tài)到底是fulfilled還是rejected。這表明,finally方法里面的操作,應(yīng)該是與狀態(tài)無(wú)關(guān)的,不依賴于 Promise 的執(zhí)行結(jié)果
6、Promise.all()
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í)例,如果不是,就會(huì)先調(diào)用下面講到的Promise.resolve方法,將參數(shù)轉(zhuǎn)為Promise實(shí)例,再進(jìn)一步處理。Promise.all方法的參數(shù)可以不是數(shù)組,但必須具有 Iterator 接口,且返回的每個(gè)成員都是 Promise 實(shí)例。)
p的狀態(tài)由1、p2、p3決定,分成兩種情況。
(1)只有p1、p2、p3的狀態(tài)都變成fulfilled,p的狀態(tài)才會(huì)變成fulfilled,此時(shí)p1、p2、p3的返回值組成一個(gè)數(shù)組,傳遞給p的回調(diào)函數(shù)。
(2)只要p1、p2、p3之中有一個(gè)被rejected,p的狀態(tài)就變成rejected,此時(shí)第一個(gè)被reject的實(shí)例的返回值,會(huì)傳遞給p的回調(diào)函數(shù)。
// 生成一個(gè)Promise對(duì)象的數(shù)組
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
上面代碼中,promises是包含 6 個(gè) Promise實(shí)例的數(shù)組,只有這 6 個(gè)實(shí)例的狀態(tài)都變成fulfilled,或者其中有一個(gè)變?yōu)?code>rejected,才會(huì)調(diào)用Promise.all方法后面的回調(diào)函數(shù)。