Promise筆記

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,簡(jiǎn)單說就是一個(gè)容器请祖,里面保存著某個(gè)未來才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果订歪。從語法上說,Promise 是一個(gè)對(duì)象肆捕,從它可以獲取異步操作的消息刷晋。Promise 提供統(tǒng)一的 API,各種異步操作都可以用同樣的方法進(jìn)行處理。

上面兩段都是阮一峰那本教程上的原話眼虱,我覺得說的算是清楚的喻奥,不知道從什么時(shí)候開始有這種感覺:如果不清楚一個(gè)東西的定義,那我對(duì)這個(gè)東西就不能說了解捏悬,雖然看了之后照著樣子也能用撞蚕,可始終也不覺得踏實(shí)。所以了解一個(gè)東西之前过牙,無論如何得知道它是什么诈豌,如果做到用自己的話嘗試給東西下個(gè)定義,那大概就做好了第一步抒和。

從上面兩段話可以得到一些信息:Promise(泛指的話)是一種異步編程的解決方案矫渔;這種方案比傳統(tǒng)的解決方案(回調(diào)函數(shù)和事件)更合理、強(qiáng)大摧莽;它已經(jīng)由ES6寫入標(biāo)準(zhǔn)庙洼,而且統(tǒng)一了用法;標(biāo)準(zhǔn)里提供 了Promise對(duì)象供我們使用镊辕,通過對(duì)這個(gè)對(duì)象的使用我們就可以實(shí)現(xiàn)這種Promise解決方案的異步編程油够。所以Promise又可以特指Promise對(duì)象,那下面說的主要就是這個(gè)Promise對(duì)象征懈。

Promise對(duì)象

那這個(gè)對(duì)象怎么生成的石咬?原生提供了Promise這個(gè)構(gòu)造函數(shù),通過這個(gè)構(gòu)造函數(shù)就可以new 出一個(gè)Promise對(duì)象實(shí)例

var promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 異步操作成功 */){
    resolve(value);
  } else {
    reject(error)
  }
})

Promise構(gòu)造函數(shù)以一個(gè)函數(shù)為參數(shù)卖哎,該函數(shù)的兩個(gè)參數(shù)分別是resolvereject鬼悠。它們是兩個(gè)函數(shù),由 JavaScript 引擎提供亏娜,不用自己部署焕窝。resolve的作用是將Promise對(duì)象的狀態(tài)從pending變?yōu)槌晒Γ瑥亩鴪?zhí)行后面的then()里的第一個(gè)參數(shù)函數(shù)维贺;reject的作用是將Promise對(duì)象的狀態(tài)從pending變?yōu)槭∷啵瑥亩鴪?zhí)行后面的catch或者then()的第二個(gè)參數(shù)函數(shù)。所以當(dāng)異步操作成功時(shí)調(diào)用resolve(value)value作為參數(shù)傳給successFn(成功時(shí)的回調(diào)函數(shù)溯泣,作為then()的第一個(gè)參數(shù))并調(diào)用之虐秋;當(dāng)異步操作失敗的時(shí)候調(diào)用reject(e)并將e作為參數(shù)傳給failureFn(失敗時(shí)的回調(diào)函數(shù),作為then()的第二個(gè)參數(shù)或catch()的參數(shù))并調(diào)用之垃沦。

舉個(gè)例子:

function timeout(ms1, ms2) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms1, ms1 + 'ms 后我會(huì)被打印出來')
    setTimeout(reject, ms2, new Error(ms2 + 'ms 后我會(huì)被打印出來'))
  });
}

timeout(3000, 4000).then((value) => {
  console.log(value)
}).catch( (e) => console.log(e))
//3s后打印 `3000ms 后我會(huì)被打印出來`

這里如果傳入的參數(shù)ms1>ms2時(shí)客给,就會(huì)調(diào)用catch((e) => console.log(e)),因?yàn)樵趍s2 ms之后先調(diào)用了setTimeout(reject, ms2, new Error(ms2 + 'ms 后我會(huì)被打印出來'))將Promise對(duì)象的狀態(tài)由pending改為失敗的狀態(tài)了栏尚。

此外起愈,除了new Promise()的方式來創(chuàng)建對(duì)象只恨,我們還可以通過Promise.resolvePromise.reject兩個(gè)方法來快速創(chuàng)建一個(gè)Promise對(duì)象:

Promise.resolve('lalala').then(console.log)

上面代碼會(huì)立即打印出'lalala',雖然目前不知道這種方式有什么用抬虽。

then()

上面說了then()方法接受兩個(gè)函數(shù)官觅,一個(gè)是異步操作成功時(shí)的回調(diào)函數(shù),一個(gè)是異步操作失敗時(shí)的回調(diào)函數(shù)阐污,兩個(gè)都可選休涤。那then()方法的返回值是什么呢?then()返回的是一個(gè)新的Promise對(duì)象笛辟,正因?yàn)榇怂晕覀兛梢詧?zhí)行一系列的鏈?zhǔn)讲僮鞴Π保瞧浞祷氐腜romise對(duì)象的行為與then()中的回調(diào)函數(shù)的返回值有關(guān),下面是MDN上的內(nèi)容:

then方法返回一個(gè)Promise手幢,而它的行為與then中的回調(diào)函數(shù)的返回值有關(guān):

  • 如果then中的回調(diào)函數(shù)返回一個(gè)值捷凄,那么then返回的Promise將會(huì)成為接受狀態(tài),并且將返回的值作為接受狀態(tài)的回調(diào)函數(shù)的參數(shù)值围来。
  • 如果then中的回調(diào)函數(shù)拋出一個(gè)錯(cuò)誤跺涤,那么then返回的Promise將會(huì)成為拒絕狀態(tài),并且將拋出的錯(cuò)誤作為拒絕狀態(tài)的回調(diào)函數(shù)的參數(shù)值监透。
  • 如果then中的回調(diào)函數(shù)返回一個(gè)已經(jīng)是接受狀態(tài)的Promise桶错,那么then返回的Promise也會(huì)成為接受狀態(tài),并且將那個(gè)Promise的接受狀態(tài)的回調(diào)函數(shù)的參數(shù)值作為該被返回的Promise的接受狀態(tài)回調(diào)函數(shù)的參數(shù)值胀蛮。
  • 如果then中的回調(diào)函數(shù)返回一個(gè)已經(jīng)是拒絕狀態(tài)的Promise院刁,那么then返回的Promise也會(huì)成為拒絕狀態(tài),并且將那個(gè)Promise的拒絕狀態(tài)的回調(diào)函數(shù)的參數(shù)值作為該被返回的Promise的拒絕狀態(tài)回調(diào)函數(shù)的參數(shù)值粪狼。
  • 如果then中的回調(diào)函數(shù)返回一個(gè)未定狀態(tài)(pending)的Promise退腥,那么then返回Promise的狀態(tài)也是未定的,并且它的終態(tài)與那個(gè)Promise的終態(tài)相同鸳玩;同時(shí)阅虫,它變?yōu)榻K態(tài)時(shí)調(diào)用的回調(diào)函數(shù)參數(shù)與那個(gè)Promise變?yōu)榻K態(tài)時(shí)的回調(diào)函數(shù)的參數(shù)是相同的。

一一舉例:

回調(diào)函數(shù)返回值為value的情況

Promise.resolve('la').then(value => value + value)
.then(value => console.log(value + value))  //打印lalalala
.then(console.log)  //打印undefined

第一個(gè)then的回調(diào)函數(shù)返回值是'lala'不跟,第二個(gè)then的回調(diào)函數(shù)返回值是 undefined,所以每個(gè)then依次執(zhí)行

回調(diào)函數(shù)拋出錯(cuò)誤的情況

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'lalala')
  });
}
timeout(1000).then(value => {throw new Error(value)})
.then(console.log, (e) => console.log(e.toString()))//打印Error:lalala

上面timeout()返回的Promise對(duì)象1000ms后執(zhí)行throw new Error(value)米碰,即then()的回調(diào)函數(shù)拋錯(cuò)窝革,然后這個(gè)錯(cuò)誤被第二個(gè)then()捕獲,并調(diào)用 (e) => console.log(e.toString())吕座,說明第一個(gè)then()返回的Promise對(duì)象因回調(diào)函數(shù)的拋錯(cuò)而變成失敗狀態(tài)

回調(diào)函數(shù)返回已經(jīng)接受狀態(tài)的Promise的情況

Promise.resolve('lala').then((value) => Promise.resolve(value + 'haha'))
.then(value => console.log(value + ': 我是從上一個(gè)then的回調(diào)函數(shù)中傳過來的')) 
//打印lalahaha: 我是從上一個(gè)then的回調(diào)函數(shù)中傳過來的

第一個(gè)then()的回調(diào)函數(shù)處理返回的是一個(gè)新的Promise對(duì)象虐译,這個(gè)對(duì)象的狀態(tài)是成功的,并且將resolve('lalahaha')的參數(shù)傳給下一個(gè)then()successFn()

回調(diào)函數(shù)返回已經(jīng)拒絕狀態(tài)的Promise的情況

Promise.resolve('lala').then((value) => Promise.reject(value + 'haha'))
.then(value => console.log(value + ': 我是從上一個(gè)then的回調(diào)函數(shù)中傳過來的')) 
.catch(value => console.log(new Error(value)))
//打印一個(gè)Error

跟第三種情況類似吴趴,不贅述

前面四種then()的回調(diào)函數(shù)的返回情況幾乎都是同步的操作漆诽,沒有涉及到異步,所以比較簡(jiǎn)單。then()的回調(diào)函數(shù)的返回的Promise對(duì)象又涉及到異步操作的情況才是常見的情況厢拭,比如我們異步訪問服務(wù)端兰英,獲取數(shù)據(jù)A,然后有根據(jù)數(shù)據(jù)A再去訪問服務(wù)端獲取數(shù)據(jù)B供鸠,下面用setTimeout來模擬異步畦贸。

回調(diào)函數(shù)返回一個(gè)未定狀態(tài)(pending)的Promise的情況

function timeout(ms, data) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, data)
  })
}

timeout(1000, 'lala').then((value) => {
  console.log(value)  //1000ms后打印lala
  return timeout(2000, 'haha' + value)
}).then(console.log) //3000ms后打印hahalala

第一個(gè)then()返回一個(gè)狀態(tài)未定的Promise對(duì)象,該P(yáng)romise對(duì)象狀態(tài)2000ms后變?yōu)槌晒顟B(tài)楞捂,所以2000ms后調(diào)用第二個(gè)then()的對(duì)應(yīng)的回調(diào)函數(shù)薄坏,而且回調(diào)函數(shù)的參數(shù)是由第一個(gè)Promise對(duì)象的resolve()傳過來的。除了這種后一個(gè)操作只依賴一個(gè)異步請(qǐng)求的情況寨闹,我們還會(huì)遇到后一步操作依賴多個(gè)異步請(qǐng)求的情況胶坠,而以來多個(gè)異步請(qǐng)求的情況又分兩種:

  • 后一步操作依賴所有異步請(qǐng)求的結(jié)果,即必須等到所有異步請(qǐng)求(為簡(jiǎn)便我們假設(shè)這些請(qǐng)求彼此互不依賴繁堡,即都是并行的)返回結(jié)果后才能執(zhí)行后一步操作
  • 后一步操作只依賴任意一個(gè)異步請(qǐng)求的結(jié)果涵但,即只需某一個(gè)異步請(qǐng)求(為簡(jiǎn)便我們假設(shè)這些請(qǐng)求彼此互不依賴,即都是并行的)返回結(jié)果后就能執(zhí)行后一步操作

對(duì)于第一種情況帖蔓,Promise對(duì)象為我們提供了.all()方法

all

Promise.all方法用于將多個(gè) Promise 實(shí)例矮瘟,包裝成一個(gè)新的 Promise 實(shí)例:
var p = Promise.all([p1, p2, p3]);
上面代碼中,Promise.all方法接受一個(gè)數(shù)組作為參數(shù)塑娇,p1澈侠、p2p3都是 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)由p1p2扎筒、p3決定莱找,只有當(dāng)p1p2嗜桌、p3的狀態(tài)都為成功的時(shí)候奥溺,p的狀態(tài)才為成功,此時(shí)p1骨宠、p2浮定、p3的返回值組成一個(gè)數(shù)組相满,傳遞給p的回調(diào)函數(shù);否則桦卒,只有要任何一個(gè)的狀態(tài)為失敗立美,那p的狀態(tài)就變成失敗,此時(shí)第一個(gè)失敗的實(shí)例的返回值闸盔,會(huì)傳遞給p的回調(diào)函數(shù)悯辙。下面看兩個(gè)例子:

function timeout(ms, data) {
  return new Promise((resolve, reject) => {
    console.log(new Date())
    setTimeout(resolve, ms, data)
  })
}
function timeout1(ms, data) {
  console.log(new Date())
  return new Promise((resolve, reject) => {
    setTimeout(reject, ms, data)
  })
}
//成功情形:
Promise.all([timeout(1000, 'kakaka'), timeout(2000, 'lalala'), timeout(3000, 'hahaha')]).then(data => console.log(data, new Date()))

/*打印
Wed Nov 01 2017 18:23:57 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
Wed Nov 01 2017 18:23:57 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
Wed Nov 01 2017 18:23:57 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
["kakaka", "lalala", "hahaha"] Wed Nov 01 2017 18:24:00 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
*/

//失敗情形:
Promise.all([timeout(1000, 'kakaka'), timeout1(2000, 'lalala'), timeout(3000, 'hahaha')]).catch(data => console.log(new Error(data), new Date()))

/*
打印
Wed Nov 01 2017 18:27:59 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
Wed Nov 01 2017 18:27:59 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
Wed Nov 01 2017 18:27:59 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)

Error: lalala
    at Promise.all.catch.data (<anonymous>:1:117)
    at <anonymous> Wed Nov 01 2017 18:28:01 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
*/

第一個(gè)為什么是3s不是6s,第二個(gè)為什么是2s不是3s迎吵,不是單線程嗎躲撰?好吧,應(yīng)該是setTimeout的機(jī)制沒搞清楚击费。下面我們來看race方法:

race

.all()不同的是拢蛋,.race()接受的數(shù)組里的Promise對(duì)象只有要任何一個(gè)改變狀態(tài),不管成功還是失敗蔫巩,就會(huì)執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù):

//成功情形:

Promise.all([timeout(1000, 'kakaka'), timeout(2000, 'lalala'), timeout(3000, 'hahaha')]).then(data => console.log(data, new Date()))

/*
打幼焕狻:
Wed Nov 01 2017 18:34:41 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
Wed Nov 01 2017 18:34:41 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
Wed Nov 01 2017 18:34:41 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
kakaka Wed Nov 01 2017 18:34:42 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
*/

//失敗情形:
Promise.all([timeout1(1000, 'kakaka'), timeout1(2000, 'lalala'), timeout(3000, 'hahaha')]).catch(data => console.log(new Error(data), new Date()))

/*
打印:
Wed Nov 01 2017 18:36:00 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
Wed Nov 01 2017 18:36:00 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
Wed Nov 01 2017 18:36:00 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
Error: kakaka
    at Promise.all.catch.data (<anonymous>:1:118)
    at <anonymous> Wed Nov 01 2017 18:36:01 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
*/

好了圆仔,這次就先到這垃瞧,后面還要再看看下Promise.resolve()Promise.reject()還需要了解原生ajax對(duì)這兩種情況的處理方式。

參考:
http://es6.ruanyifeng.com/#docs/promise
http://www.cnblogs.com/lvdabao/p/es6-promise-1.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坪郭,一起剝皮案震驚了整個(gè)濱河市个从,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌歪沃,老刑警劉巖嗦锐,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異沪曙,居然都是意外死亡奕污,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門液走,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碳默,“玉大人,你說我怎么就攤上這事育灸∧逯希” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵磅崭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我瓦哎,道長(zhǎng)砸喻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮号胚,結(jié)果婚禮上诀蓉,老公的妹妹穿的比我還像新娘。我一直安慰自己癣漆,他們只是感情好维咸,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著惠爽,像睡著了一般癌蓖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上婚肆,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天租副,我揣著相機(jī)與錄音,去河邊找鬼较性。 笑死用僧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赞咙。 我是一名探鬼主播责循,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼攀操!你這毒婦竟也來了院仿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤崔赌,失蹤者是張志新(化名)和其女友劉穎意蛀,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體健芭,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡县钥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了慈迈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片若贮。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖痒留,靈堂內(nèi)的尸體忽然破棺而出谴麦,到底是詐尸還是另有隱情,我是刑警寧澤伸头,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布匾效,位于F島的核電站,受9級(jí)特大地震影響恤磷,放射性物質(zhì)發(fā)生泄漏面哼。R本人自食惡果不足惜野宜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望魔策。 院中可真熱鬧匈子,春花似錦、人聲如沸闯袒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽政敢。三九已至其徙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間堕仔,已是汗流浹背擂橘。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留摩骨,地道東北人通贞。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像恼五,于是被迫代替她去往敵國和親昌罩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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