接上一篇痘煤,繼續(xù)介紹了 Promise 相關(guān) API。
一城舞、Promise.resolve()
Promise.resolve()
方法的作用就是將某個值(非 Promise
對象實例)轉(zhuǎn)換為 Promise
對象實例疚脐。
const promise = Promise.resolve('foo')
// 相當(dāng)于
const promise = new Promise(resolve => resolve('foo'))
需要注意的是,它仍然會遵循 Event Loop 機(jī)制弦聂,包括后面介紹的其他 API。具體執(zhí)行順序本文不展開討論氛什。
Promise.resolve()
方法的參數(shù)分為四種情況:
1. 不帶任何參數(shù)
它返回一個狀態(tài)為 fulfilled
莺葫,值為 undefined
的 Promise
實例對象。
const promise = Promise.resolve()
// promise 結(jié)果:
// {
// [[Prototype]]: Promise,
// [[PromiseState]]: "fulfilled",
// [[PromiseResult]]: undefined
// }
2. 參數(shù)是一個 Promise 實例對象
這時枪眉,Promise.resolve()
將會不做任何修改捺檬、原封不動地返回該實例。
請注意贸铜,即使參數(shù)是一個 rejected
狀態(tài)的 Promise
實例堡纬,返回的實例也不會變成 fulfilled
狀態(tài),不要被這個 resolve
字面意思誤解了蒿秦。
const p1 = new Promise(resolve => resolve({ name: 'Frankie' })) // "fulfilled"
const p2 = new Promise((resolve, reject) => reject({ name: 'Frankie' })) // "rejected"
const p3 = Promise.resolve(p1)
const p4 = Promise.resolve(p2)
console.log(p1 === p3) // true
console.log(p2 === p4) // true
// p3 結(jié)果:
// {
// [[Prototype]]: Promise,
// [[PromiseState]]: "fulfilled",
// [[PromiseResult]]: { name: 'Frankie' }
// }
// p4 結(jié)果:
// {
// [[Prototype]]: Promise,
// [[PromiseState]]: "rejected",
// [[PromiseResult]]: { name: 'Frankie' }
// }
其實這種情況烤镐,就是上一篇提到過的。
const p5 = new Promise(resolve => resovle(1))
const p6 = new Promise(resolve => {
reslove(p5)
// 注意棍鳖,不要嘗試在此處調(diào)用 Promise.resolve()炮叶,會導(dǎo)致無限遞歸。
})
上面示例中渡处,p6
的狀態(tài)取決于 p5
的狀態(tài)镜悉。
3. 參數(shù)是一個 thenable 對象
thenable
對象,是指具有 then
方法的對象医瘫。例如:
const obj = {
then: function(resolve, reject) {
resolve('foo')
}
}
上面示例中侣肄,obj
對象就是一個 thenable
對象。Promise.resolve()
方法會將這個 thenable
對象轉(zhuǎn)為 Promise
對象醇份,然后就立即執(zhí)行 thenable
對象的 then()
方法稼锅。
const obj = {
then: function (resolve, reject) {
console.log(2)
resolve('foo')
// reject('foo') // 如果是這樣吼具,最終 promise 對象將會變成了 rejected 狀態(tài)。
}
}
const promise = Promise.resolve(obj)
promise.then(res => {
console.log(3)
console.log(res) // "foo"
})
console.log(1)
// 打印結(jié)果分別是 1矩距、2拗盒、3、"foo"
// promise 結(jié)果:
// {
// [[Prototype]]: Promise,
// [[PromiseState]]: "fulfilled",
// [[PromiseResult]]: "foo"
// }
上述示例中剩晴,obj
對象的 then()
方法執(zhí)行后锣咒,對象 promise
的狀態(tài)變成了 fulfilled
侵状,接著執(zhí)行最后的那個 promise.then()
方法赞弥,打印出 "foo"
。
4. 參數(shù)是一個不具有 then() 方法的對象趣兄,或者壓根不是一個對象绽左,而是原始值。
如果是這種情況艇潭,Promise.resolve()
方法返回一個新的 Promise
對象拼窥,狀態(tài)為 fulfilled
,且該實例對象的值蹋凝,就是該參數(shù)值鲁纠。
const p1 = Promise.resolve('foo')
const p2 = Promise.resolve({})
// p1 結(jié)果:
// {
// [[Prototype]]: Promise,
// [[PromiseState]]: "fulfilled",
// [[PromiseResult]]: "foo"
// }
// p2 結(jié)果:
// {
// [[Prototype]]: Promise,
// [[PromiseState]]: "fulfilled",
// [[PromiseResult]]: {}
// }
在實際項目中,一般是第 4 種情況居多鳍寂,我似乎真的沒見過前三種情況的改含。
二、Promise.reject()
Promise.reject()
方法會返回一個新的 Promise
實例對象迄汛,該實例的狀態(tài)總是為 rejected
捍壤。
const promise = Promise.reject('foo')
// 相當(dāng)于
const promise = new Promise((resolve, reject) => reject('foo'))
跟 Promise.resolve()
不同的是,Promise.reject()
方法的參數(shù)(無論是原始值鞍爱、普通對象鹃觉、還是 Promise
實例對象),將會原封不動地作為返回實例對象的值睹逃。
Promise.reject('Oops').catch(err => {
console.log(err === 'Oops') // true
// do something...
})
三盗扇、Promise.all()
Promise.all()
方法用于將多個 Promise
實例,包裝成一個新的 Promise
實例沉填。
const promise = Promise.all([p1, p2, p3])
上面代碼中粱玲,Promise.all()
方法接受一個數(shù)組作為參數(shù),其中 p1
拜轨、p2
抽减、p3
都是 Promise
實例。如果數(shù)組中包含非 Promise
實例橄碾,它們會使用 Promise.resolve()
的方法卵沉,將參數(shù)轉(zhuǎn)為 Promise
實例颠锉,再進(jìn)一步處理。另外史汗,Promise.all()
方法的參數(shù)可以不是數(shù)組琼掠,但必須具有 Iterator 接口,且返回的每個成員都是 Promise
實例停撞。
其中 promise
的狀態(tài)由 p1
瓷蛙、p2
、p3
決定戈毒,分為兩種情況艰猬。
只有當(dāng)
p1
、p2
埋市、p3
的狀態(tài)都變成fulfilled
冠桃,promise
的狀態(tài)才會變成fulfilled
,此時p1
道宅、p2
食听、p3
實例的值,會組成一個數(shù)組污茵,并傳遞給promise
樱报。只要
p1
、p2
泞当、p3
之中有一個被rejected
迹蛤,promise
的狀態(tài)就會變成rejected
。此時第一個rejected
實例的值(注意零蓉,不會像上面一樣組成數(shù)組哦)笤受,會傳遞給promise
。
看個例子:
const userIds = [1, 3, 5]
const promiseArr = userIds.map(id => {
return window.fetch(`/config/user/${id}`) // 假設(shè)是請求用戶配置
})
Promise
.all(promiseArr)
.then(res => {
// res 是一個數(shù)組敌蜂,每一項對應(yīng)每個實例的值想鹰,即 [[PromiseResult]]
// 常見做法是將 res 進(jìn)行解構(gòu)贵少,即 Promise.all(promiseArr).then(([a, b, c]) => { /* do something... */ })
// 假設(shè) promiseArr 是一個空的可迭代對象,例如空數(shù)組,Promise.all([]) 實例狀態(tài)為 fulfilled莺匠,值為 []彬檀。
// do something...
})
.catch(err => {
// err 為 Promise.all() 被 rejected 的原因(reason)
})
上面的示例中姨蝴,promiseArr
是包含 3 個 Promise
實例的數(shù)組焊傅,只有這 3 個實例的狀態(tài)都變成 fulfilled
,或其中一個變?yōu)?rejected
摊唇,才會調(diào)用 Promise.all()
方法的回調(diào)函數(shù)咐蝇。
四、Promise.race()
Promise.race()
方法同樣是將多個 Promise
實例巷查,包裝成一個新的 Promise
實例有序。
const promise = Promise.race([p1, p2, p3])
Promise.race()
方法同樣接受一個可迭代對象抹腿,只要 p1
、p2
旭寿、p3
中有一個實例率先改變狀態(tài)(fulfiled
或 rejected
)警绩,promise
的狀態(tài)就會跟著改變,而且 promise
實例的值就是率先改變的實例的返回值盅称。若可迭代對象中的某一項不是 Promise
實例肩祥,仍會使用 Promise.resolve()
進(jìn)行轉(zhuǎn)換。
當(dāng)傳遞一個空的可迭代對象缩膝,那么 Promise.race()
實例的狀態(tài)將會一直停留在 pending
混狠。這點跟 Promise.all()
是不同的。
const p1 = Promise.all([])
const p2 = Promise.race([])
setTimeout(() => {
console.log(p1) // Promise {<fulfilled>: Array(0)}
console.log(p2) // Promise {<pending>}
})
五逞盆、Promise.allSettled()
Promise.allSettled()
是 ES11 標(biāo)準(zhǔn)引入的一個方法檀蹋,同樣還是將多個 Promise
實例包裝成一個新的 Promise
實例松申。只有等所有實例都返回結(jié)果(無論是 fulfilled
還 rejected
)云芦,包裝實例的狀態(tài)才會發(fā)生變化。
我認(rèn)為贸桶,這算是對 Promise.all()
存在 rejected
實例情況的一種補全吧舅逸。
注意,
Promise.allSettled()
的狀態(tài)皇筛,只可能是pending
或fulfilled
狀態(tài)琉历,不可能存在rejected
狀態(tài)。即只會從pending -> fulfilled
的變化水醋。
我們來看看以下示例旗笔,各種情況的結(jié)果吧:
const p1 = Promise.reject(1)
const p2 = Promise.resolve(2)
const p3 = new Promise((resolve, reject) => {
setTimeout(() => { reject(3) }, 1000)
})
const p4 = new Promise(() => { }) // p4 狀態(tài)將會一直停留在 pending
const p5 = Promise.allSettled([]) // 參數(shù)為空迭代對象
const p6 = Promise.allSettled([p4])
const p7 = Promise.allSettled([p1, p2, p3])
setTimeout(() => {
console.log('p1:', p1)
console.log('p2:', p2)
console.log('p3:', p3)
console.log('p4:', p4)
console.log('p5:', p5)
console.log('p6:', p6)
console.log('p7:', p7)
p5.then(res => {
console.log('p5 then:', res)
})
p6.then(res => {
// 這里將不會執(zhí)行,因為 p6 一直處于 pending 狀態(tài)
console.log('p6 then:', res)
})
p7.then(res => {
console.log('p7 then:', res)
})
}, 2000)
列舉以上示例拄踪,是為了得出以下結(jié)論:
Promise.allSettled()
一定要等到參數(shù)中每一個Promise
狀態(tài)定型后蝇恶,它返回的實例對象才會定型為fulfilled
狀態(tài)。否則只會是pending
狀態(tài)惶桐。類似
Promise.allSettled([])
把一個空數(shù)組(空的迭代對象)作為參數(shù)撮弧,最后實例的狀態(tài)為fulfilled
,且實例的值為空數(shù)組[]
姚糊。-
注意
Promise.allSettled()
返回的實例的值贿衍,首先它是一個數(shù)組,而數(shù)組每項都是一個對象救恨,該對象的屬性取決于對應(yīng)參數(shù)Promise
實例的狀態(tài)贸辈。例如
p1
的狀態(tài)為rejected
,p2
的狀態(tài)為fulfilled
肠槽。因此包裝實例的前兩項的對象分別為{ status: "rejected", reason: 1 }
擎淤、{ status: "fulfilled", value: 2 }
躏哩,其他項同理。其中status
屬性只會是fulfilled
或rejected
兩個字符串值揉燃。主要區(qū)別在于value
屬性和reason
屬性扫尺,即fulfilled
狀態(tài)對應(yīng)value
屬性,而rejected
狀態(tài)對應(yīng)reason
屬性炊汤。
有時候正驻,我們不關(guān)心異步操作的結(jié)果,只關(guān)心這些操作有沒有結(jié)束抢腐。這時姑曙,使用 Promise.allSettled()
方法就很有用了。而 Promise.all()
是沒辦法確保這一點的迈倍。
六伤靠、Promise.any()
在 ES12 標(biāo)準(zhǔn)中,引入了 Promise.any()
方法啼染,它用于將多個 Promise
實例宴合,包裝成一個新的 Promise
實例。
Promise.any()
接受一個 Promise
可迭代對象迹鹅,只要參數(shù)實例中有一個變成 fulfilled
狀態(tài)卦洽,包裝實例就會變成 fulfilled
狀態(tài),其值就是參數(shù)實例的值斜棚。
Promise.any()
與 Promise.race()
很像阀蒂,只有一個不同點,就是 Promise.any()
不會因為某個參數(shù) Promise
實例變成 rejected
狀態(tài)而接受弟蚀,必須要等到所有參數(shù)實例的狀態(tài)都變?yōu)?rejected
蚤霞,包裝實例的狀態(tài)才會是 rejected
。
const p1 = Promise.reject(1)
const p2 = Promise.resolve(2)
const p3 = new Promise((resolve, reject) => {
setTimeout(() => { reject(3) }, 1000)
})
const p4 = new Promise(() => { }) // p4 狀態(tài)將會一直停留在 pending
const p5 = Promise.any([]) // p5 會變成 rejected 狀態(tài)
const p6 = Promise.any([p4])
const p7 = Promise.any([p1, p2, p3])
const p8 = Promise.any([p1, p3])
setTimeout(() => {
console.log('p1:', p1)
console.log('p2:', p2)
console.log('p3:', p3)
console.log('p4:', p4)
console.log('p5:', p5)
console.log('p6:', p6)
console.log('p7:', p7)
p5.then(res => {
console.log('p5 then:', res)
}).catch(err => {
// p5 的狀態(tài)會變成 rejected义钉,因此會執(zhí)行到這里昧绣。
console.log('p5 catch:', err)
})
p6.then(res => {
// p6 的狀態(tài)一直會是 pending,因此不會執(zhí)行回調(diào)断医。
console.log('p6 then:', res)
})
p7.then(res => {
console.log('p7 then:', res)
})
p8.then(res => {
console.log('p8 then:', res)
}).catch(err => {
// 注意 err 是一個對象
console.log('p8 catch:', err)
console.dir(err)
})
}, 2000)
當(dāng) Promise.any()
返回的實例變成 rejected
時滞乙,其實例的值是 AggregateError 實例。但傳遞一個空的迭代對象鉴嗤,Promise.any()
包裝實例也會變成 rejected
狀態(tài)斩启,如 p5
。
七醉锅、總結(jié)
關(guān)于 Promise.all()
兔簇、Promise.race()
、Promise.allSettled()
、Promise.any()
方法垄琐,總結(jié)以下特點边酒。
它們的用處都是將多個
Promise
實例,包裝成一個新的Promise
實例狸窘。它們都接受一個具有 Iterator 接口的可迭代對象墩朦,通常為數(shù)組。且會返回一個新的
Promise
實例對象翻擒。它們處理參數(shù)為空的可迭代對象的方式不一樣氓涣,本來就是要處理多個
Promise
對象,才會用到它們陋气,所以這種情況無需理會劳吠。真遇到再回來翻閱文檔即可,現(xiàn)在我寫到這里都記不太清楚其中的區(qū)別了巩趁,但問題不大痒玩。
Promise.all()
當(dāng)所有實例均為fulfilled
狀態(tài),最終的包裝實例才會是fulfilled
议慰,其值是一個數(shù)組蠢古。否則將會是rejected
狀態(tài);
Promise.race()
則是某個實例的狀態(tài)發(fā)生變化褒脯,最終包裝實例將對應(yīng)率先變化實例所對應(yīng)的值和狀態(tài)便瑟±禄伲“發(fā)生變化”是指pending -> fulfilled
或pending -> rejected
番川。
Promise.allSettled()
單從命名上來猜測,就知道它需要等所有參數(shù)實例確定狀態(tài)后脊框,包裝實例的狀態(tài)才會變成fulfilled
狀態(tài)颁督,注意它不存在rejected
狀態(tài)的情況。包裝實例的返回值是一個數(shù)組浇雹,數(shù)組每項可能是{ status: "fulfilled", value: /* 對應(yīng) fulfilled 的值 */ }
或{ status: "rejected", reason: /* 對應(yīng) rejected 的原因 */ }
沉御,取決于每個參數(shù)實例的狀態(tài)。
Promise.any()
當(dāng)某個參數(shù)實例的狀態(tài)變?yōu)?fulfilled
昭灵,那么包裝實例就定型了吠裆,對應(yīng)該參數(shù)實例的狀態(tài)和值。否則它必須等到所有參數(shù)實例變?yōu)?rejected
狀態(tài)烂完,包裝實例的狀態(tài)才會發(fā)生改變试疙,變?yōu)?rejected
,其值是一個AggregateError
實例抠蚣。
The end.