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ù)分別是resolve
和reject
鬼悠。它們是兩個(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.resolve
和Promise.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
澈侠、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
決定莱找,只有當(dāng)p1
、p2
嗜桌、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