Promise 對(duì)象

Promise 的含義

Promise 是異步編程的一種解決方案或渤,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大系冗。它由社區(qū)最早提出和實(shí)現(xiàn),ES6 將其寫進(jìn)了語言標(biāo)準(zhǔn)薪鹦,統(tǒng)一了用法掌敬,原生提供了Promise對(duì)象。
所謂Promise池磁,簡單說就是一個(gè)容器奔害,里面保存著某個(gè)未來才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果。從語法上說地熄,Promise 是一個(gè)對(duì)象华临,從它可以獲取異步操作的消息。Promise 提供統(tǒng)一的 API端考,各種異步操作都可以用同樣的方法進(jìn)行處理雅潭。
Promise對(duì)象有以下兩個(gè)特點(diǎn):

  • 對(duì)象的狀態(tài)不受外界影響。Promise對(duì)象代表一個(gè)異步操作却特,有三種狀態(tài):pending(進(jìn)行中)扶供、fulfilled(已成功)和rejected(已失敗)核偿。只有異步操作的結(jié)果诚欠,可以決定當(dāng)前是哪一種狀態(tài),任何其他操作都無法改變這個(gè)狀態(tài)漾岳。這也是Promise這個(gè)名字的由來轰绵,它的英語意思就是“承諾”,表示其他手段無法改變尼荆。
  • 一旦狀態(tài)改變左腔,就不會(huì)再變,任何時(shí)候都可以得到這個(gè)結(jié)果捅儒。Promise對(duì)象的狀態(tài)改變液样,只有兩種可能:從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected振亮。只要這兩種情況發(fā)生,狀態(tài)就凝固了鞭莽,不會(huì)再變了坊秸,會(huì)一直保持這個(gè)結(jié)果,這時(shí)就稱為 resolved(已定型)澎怒。如果改變已經(jīng)發(fā)生了褒搔,你再對(duì)Promise對(duì)象添加回調(diào)函數(shù),也會(huì)立即得到這個(gè)結(jié)果喷面。這與事件(Event)完全不同星瘾,事件的特點(diǎn)是,如果你錯(cuò)過了它惧辈,再去監(jiān)聽,是得不到結(jié)果的

Promise也有一些缺點(diǎn)盒齿。首先,無法取消Promise县昂,一旦新建它就會(huì)立即執(zhí)行,無法中途取消倒彰。其次莱睁,如果不設(shè)置回調(diào)函數(shù),Promise內(nèi)部拋出的錯(cuò)誤仰剿,不會(huì)反應(yīng)到外部。第三南吮,當(dāng)處于pending狀態(tài)時(shí),無法得知目前進(jìn)展到哪一個(gè)階段(剛剛開始還是即將完成)部凑。

如果某些事件不斷地反復(fù)發(fā)生,一般來說涂邀,使用 Stream 模式是比部署Promise更好的選擇

基本用法

ES6 規(guī)定瘟仿,Promise對(duì)象是一個(gè)構(gòu)造函數(shù),用來生成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)?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)閞esolved時(shí)調(diào)用,第二個(gè)回調(diào)函數(shù)是Promise對(duì)象的狀態(tài)變?yōu)閞ejected時(shí)調(diào)用忽洛。其中腻惠,第二個(gè)函數(shù)是可選的,不一定要提供欲虚。這兩個(gè)函數(shù)都接受Promise對(duì)象傳出的值作為參數(shù)集灌。

Promise.prototype.then()

Promise 實(shí)例具有then方法,也就是說复哆,then方法是定義在原型對(duì)象Promise.prototype上的欣喧。它的作用是為 Promise 實(shí)例添加狀態(tài)改變時(shí)的回調(diào)函數(shù)。前面說過梯找,then方法的第一個(gè)參數(shù)是resolved狀態(tài)的回調(diào)函數(shù)唆阿,第二個(gè)參數(shù)(可選)是rejected狀態(tài)的回調(diào)函數(shù)。

then方法返回的是一個(gè)新的Promise實(shí)例(注意锈锤,不是原來那個(gè)Promise實(shí)例)驯鳖。因此可以采用鏈?zhǔn)綄懛ǎ磘hen方法后面再調(diào)用另一個(gè)then方法久免。

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});
// 上面的代碼使用then方法,依次指定了兩個(gè)回調(diào)函數(shù)摔握。第一個(gè)回調(diào)函數(shù)完成以后丁寄,會(huì)將返回結(jié)果作為參數(shù)泊愧,傳入第二個(gè)回調(diào)函數(shù)删咱。

采用鏈?zhǔn)降膖hen痰滋,可以指定一組按照次序調(diào)用的回調(diào)函數(shù)敲街。這時(shí)严望,前一個(gè)回調(diào)函數(shù)像吻,有可能返回的還是一個(gè)Promise對(duì)象(即有異步操作),這時(shí)后一個(gè)回調(diào)函數(shù)姆涩,就會(huì)等待該P(yáng)romise對(duì)象的狀態(tài)發(fā)生變化骨饿,才會(huì)被調(diào)用

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)閞esolved蛛碌,則會(huì)調(diào)用then方法指定的回調(diào)函數(shù)蔚携;如果異步操作拋出錯(cuò)誤,狀態(tài)就會(huì)變?yōu)閞ejected誊辉,就會(huì)調(diào)用catch方法指定的回調(diào)函數(shù)亡脑,處理這個(gè)錯(cuò)誤霉咨。另外途戒,then方法指定的回調(diào)函數(shù),如果運(yùn)行中拋出錯(cuò)誤裁蚁,也會(huì)被catch方法捕獲

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ù)入篮。

finally方法的回調(diào)函數(shù)不接受任何參數(shù)潮售,這意味著沒有辦法知道锅风,前面的 Promise 狀態(tài)到底是fulfilled還是rejected皱埠。這表明,finally方法里面的操作训枢,應(yīng)該是與狀態(tài)無關(guān)的,不依賴于 Promise 的執(zhí)行結(jié)果诀黍。
<b>finally本質(zhì)上是then方法的特例眯勾。

promise
.finally(() => {
  // 語句
});

// 等同于
promise
.then(
  result => {
    // 語句
    return result;
  },
  error => {
    // 語句
    throw error;
  }
);
// 如果不使用finally方法婆誓,同樣的語句需要為成功和失敗兩種情況各寫一次洋幻。有了finally方法,則只需要寫一次好唯。

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)由p1辰企、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ù)拱层。

Promise.race()

Promise.race方法同樣是將多個(gè) Promise 實(shí)例根灯,包裝成一個(gè)新的 Promise 實(shí)例

const p = Promise.race([p1, p2, p3]);
```
Promise.race方法的參數(shù)與Promise.all方法一樣掺栅,如果不是 Promise 實(shí)例氧卧,就會(huì)先調(diào)用下面講到的Promise.resolve方法沙绝,將參數(shù)轉(zhuǎn)為 Promise 實(shí)例星著,再進(jìn)一步處理

###Promise.resolve()
有時(shí)需要將現(xiàn)有對(duì)象轉(zhuǎn)為 Promise 對(duì)象粗悯,Promise.resolve方法就起到這個(gè)作用
```
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
// 將 jQuery 生成的deferred對(duì)象样傍,轉(zhuǎn)為一個(gè)新的 Promise 對(duì)象
```
Promise.resolve等價(jià)于下面的寫法
```
Promise.resolve('foo')
// 等價(jià)于
new Promise(resolve => resolve('foo'))
```
Promise.resolve方法的參數(shù)分成四種情況:

<b>(1)參數(shù)是一個(gè) Promise 實(shí)例

如果參數(shù)是 Promise 實(shí)例衫哥,那么Promise.resolve將不做任何修改撤逢、原封不動(dòng)地返回這個(gè)實(shí)例捌斧。

<b>(2)參數(shù)是一個(gè)thenable對(duì)象

thenable對(duì)象指的是具有then方法的對(duì)象,比如下面這個(gè)對(duì)象跷究。
```
let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};
/* Promise.resolve方法會(huì)將這個(gè)對(duì)象轉(zhuǎn)為 Promise 對(duì)象俊马,然后就立即執(zhí)行thenable對(duì)象的then方法肩杈。*/
let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});
/* 上面代碼中艘儒,thenable對(duì)象的then方法執(zhí)行后界睁,對(duì)象p1的狀態(tài)就變?yōu)閞esolved兵拢,從而立即執(zhí)行最后那個(gè)then方法指定的回調(diào)函數(shù),輸出 42访惜。*/
```
<b>(3)參數(shù)不是具有then方法的對(duì)象,或根本就不是對(duì)象

如果參數(shù)是一個(gè)原始值腻扇,或者是一個(gè)不具有then方法的對(duì)象债热,則Promise.resolve方法返回一個(gè)新的 Promise 對(duì)象,狀態(tài)為resolved衙解。
```
const p = Promise.resolve('Hello');
p.then(function (s){
  console.log(s)
});
// Hello
/* 上面代碼生成一個(gè)新的 Promise 對(duì)象的實(shí)例p阳柔。由于字符串Hello不屬于異步操作(判斷方法是字符串對(duì)象不具有 then 方法),返回 Promise 實(shí)例的狀態(tài)從一生成就是resolved蚓峦,所以回調(diào)函數(shù)會(huì)立即執(zhí)行舌剂。Promise.resolve方法的參數(shù),會(huì)同時(shí)傳給回調(diào)函數(shù)暑椰。*/
```
<b>(4)不帶有任何參數(shù)

Promise.resolve方法允許調(diào)用時(shí)不帶參數(shù)霍转,直接返回一個(gè)resolved狀態(tài)的 Promise 對(duì)象。
所以避消,如果希望得到一個(gè) Promise 對(duì)象,比較方便的方法就是直接調(diào)用Promise.resolve方法婶溯。
```
const p = Promise.resolve();
p.then(function () {
  // ...
});
// 上面代碼的變量p就是一個(gè) Promise 對(duì)象类少。
```
需要注意的是妓忍,立即resolve的 Promise 對(duì)象,是在本輪“事件循環(huán)”(event loop)的結(jié)束時(shí)祖凫,而不是在下一輪“事件循環(huán)”的開始時(shí)稠屠。
```
setTimeout(function () {
  console.log('three');
}, 0);
Promise.resolve().then(function () {
  console.log('two');
});
console.log('one');
// one
// two
// three
上面代碼中煎谍,setTimeout(fn, 0)在下一輪“事件循環(huán)”開始時(shí)執(zhí)行转捕,Promise.resolve()在本輪“事件循環(huán)”結(jié)束時(shí)執(zhí)行辕万,console.log('one')則是立即執(zhí)行丑念,因此最先輸出。
```
###Promise.reject()
Promise.reject(reason)方法也會(huì)返回一個(gè)新的 Promise 實(shí)例恍涂,該實(shí)例的狀態(tài)為rejected
```
const p = Promise.reject('出錯(cuò)了');
// 等同于
const p = new Promise((resolve, reject) => reject('出錯(cuò)了'))
p.then(null, function (s) {
  console.log(s)
});
// 出錯(cuò)了
// 上面代碼生成一個(gè) Promise 對(duì)象的實(shí)例p,狀態(tài)為rejected,回調(diào)函數(shù)會(huì)立即執(zhí)行。
```
注意,Promise.reject()方法的參數(shù)雕擂,會(huì)原封不動(dòng)地作為reject的理由流部,變成后續(xù)方法的參數(shù)果漾。這一點(diǎn)與Promise.resolve方法不一致捍歪。
```
const thenable = {
  then(resolve, reject) {
    reject('出錯(cuò)了');
  }
};
Promise.reject(thenable)
.catch(e => {
  console.log(e === thenable)
})
// true
// 上面代碼中必逆,Promise.reject方法的參數(shù)是一個(gè)thenable對(duì)象,執(zhí)行以后渊啰,后面catch方法的參數(shù)不是reject拋出的“出錯(cuò)了”這個(gè)字符串,而是thenable對(duì)象胞枕。
```
###Promise.try()
實(shí)際開發(fā)中,經(jīng)常遇到一種情況:不知道或者不想?yún)^(qū)分员魏,函數(shù)f是同步函數(shù)還是異步操作闻书,但是想用 Promise 來處理它晃择。因?yàn)檫@樣就可以不管f是否包含異步操作告材,都用then方法指定下一步流程闷堡,用catch方法處理f拋出的錯(cuò)誤缚窿。一般就會(huì)采用下面的寫法
```
Promise.resolve().then(f)
// -------------------------------
// 上面的寫法有一個(gè)缺點(diǎn)育瓜,就是如果f是同步函數(shù)躏仇,那么它會(huì)在本輪事件循環(huán)的末尾執(zhí)行书妻。
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
// 函數(shù)f是同步的工猜,但是用 Promise 包裝了以后李皇,就變成異步執(zhí)行
```
<b>讓同步函數(shù)同步執(zhí)行粥烁,異步函數(shù)異步執(zhí)行,并且讓它們具有統(tǒng)一的 API 
* 第一種寫法是用async函數(shù)來寫
```
const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next
// 第二行是一個(gè)立即執(zhí)行的匿名函數(shù)蝇棉,會(huì)立即執(zhí)行里面的async函數(shù)讨阻,因此如果f是同步的,就會(huì)得到同步的結(jié)果篡殷;如果f是異步的钝吮,就可以用then指定下一步,就像下面的寫法

(async () => f())()
.then(...)

```
需要注意的是板辽,async () => f()會(huì)吃掉f()拋出的錯(cuò)誤奇瘦。所以,如果想捕獲錯(cuò)誤劲弦,要使用promise.catch方法链患。
```
(async () => f())()
.then(...)
.catch(...)
```
* 第二種寫法是使用new Promise()
```
const f = () => console.log('now');
(
  () => new Promise(
    resolve => resolve(f())
  )
)();
console.log('next');
// now
// next
// 上面代碼也是使用立即執(zhí)行的匿名函數(shù),執(zhí)行new Promise()瓶您。這種情況下,同步函數(shù)也是同步執(zhí)行的纲仍。
```
鑒于這是一個(gè)很常見的需求呀袱,所以現(xiàn)在有一個(gè)提案,提供Promise.try方法替代上面的寫法郑叠。
```
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
```
事實(shí)上夜赵,Promise.try就是模擬try代碼塊,就像promise.catch模擬的是catch代碼塊

注:本文內(nèi)容整理自阮一峰《ECMAScript6 入門》一書乡革,如有侵權(quán)請(qǐng)聯(lián)系刪除寇僧。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市沸版,隨后出現(xiàn)的幾起案子嘁傀,更是在濱河造成了極大的恐慌,老刑警劉巖视粮,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件细办,死亡現(xiàn)場離奇詭異,居然都是意外死亡蕾殴,警方通過查閱死者的電腦和手機(jī)笑撞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門岛啸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人茴肥,你說我怎么就攤上這事坚踩。” “怎么了瓤狐?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵瞬铸,是天一觀的道長。 經(jīng)常有香客問我芬首,道長赴捞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任郁稍,我火速辦了婚禮赦政,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘耀怜。我一直安慰自己恢着,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布财破。 她就那樣靜靜地躺著掰派,像睡著了一般。 火紅的嫁衣襯著肌膚如雪左痢。 梳的紋絲不亂的頭發(fā)上靡羡,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音俊性,去河邊找鬼略步。 笑死,一個(gè)胖子當(dāng)著我的面吹牛定页,可吹牛的內(nèi)容都是我干的趟薄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼典徊,長吁一口氣:“原來是場噩夢啊……” “哼杭煎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起卒落,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤羡铲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后儡毕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體犀勒,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贾费。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钦购。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖褂萧,靈堂內(nèi)的尸體忽然破棺而出押桃,到底是詐尸還是另有隱情,我是刑警寧澤导犹,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布唱凯,位于F島的核電站,受9級(jí)特大地震影響谎痢,放射性物質(zhì)發(fā)生泄漏磕昼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一节猿、第九天 我趴在偏房一處隱蔽的房頂上張望票从。 院中可真熱鬧,春花似錦滨嘱、人聲如沸峰鄙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吟榴。三九已至,卻和暖如春囊扳,著一層夾襖步出監(jiān)牢的瞬間吩翻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工锥咸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留狭瞎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓她君,卻偏偏與公主長得像,于是被迫代替她去往敵國和親葫哗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缔刹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容