細(xì)讀 ES6 | Promise 下篇

配圖源自 Freepik

上一篇痘煤,繼續(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莺葫,值為 undefinedPromise 實例對象。

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瓷蛙、p2p3 決定戈毒,分為兩種情況艰猬。

  • 只有當(dāng) p1p2埋市、p3 的狀態(tài)都變成 fulfilled冠桃,promise 的狀態(tài)才會變成 fulfilled,此時 p1道宅、p2食听、p3 實例的值,會組成一個數(shù)組污茵,并傳遞給 promise樱报。

  • 只要 p1p2泞当、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() 方法同樣接受一個可迭代對象抹腿,只要 p1p2旭寿、p3 中有一個實例率先改變狀態(tài)(fulfiledrejected)警绩,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é)果(無論是 fulfilledrejected)云芦,包裝實例的狀態(tài)才會發(fā)生變化。

我認(rèn)為贸桶,這算是對 Promise.all() 存在 rejected 實例情況的一種補全吧舅逸。

注意,Promise.allSettled() 的狀態(tài)皇筛,只可能是 pendingfulfilled 狀態(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)為 rejectedp2 的狀態(tài)為 fulfilled肠槽。因此包裝實例的前兩項的對象分別為 { status: "rejected", reason: 1 }擎淤、{ status: "fulfilled", value: 2 }躏哩,其他項同理。其中 status 屬性只會是 fulfilledrejected 兩個字符串值揉燃。主要區(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 -> fulfilledpending -> 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.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末祝旷,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌怀跛,老刑警劉巖距贷,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吻谋,居然都是意外死亡忠蝗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門漓拾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來什湘,“玉大人,你說我怎么就攤上這事晦攒∶龀罚” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵脯颜,是天一觀的道長哟旗。 經(jīng)常有香客問我,道長栋操,這世上最難降的妖魔是什么闸餐? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮矾芙,結(jié)果婚禮上舍沙,老公的妹妹穿的比我還像新娘。我一直安慰自己剔宪,他們只是感情好拂铡,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著葱绒,像睡著了一般感帅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上地淀,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天失球,我揣著相機(jī)與錄音,去河邊找鬼帮毁。 笑死实苞,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的烈疚。 我是一名探鬼主播黔牵,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼胞得!你這毒婦竟也來了荧止?” 一聲冷哼從身側(cè)響起屹电,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎跃巡,沒想到半個月后危号,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡素邪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年外莲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兔朦。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡偷线,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沽甥,到底是詐尸還是另有隱情声邦,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布摆舟,位于F島的核電站亥曹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏恨诱。R本人自食惡果不足惜媳瞪,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望照宝。 院中可真熱鬧蛇受,春花似錦、人聲如沸厕鹃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽熊响。三九已至旨别,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間汗茄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工铭若, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留洪碳,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓叼屠,卻偏偏與公主長得像瞳腌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子镜雨,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

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

  • Promise含義 Promise是異步編程的一種解決方案嫂侍,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更強(qiáng)大。所謂Pr...
    oWSQo閱讀 1,085評論 0 4
  • Promise 含義 Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)...
    Upcccz閱讀 221評論 0 0
  • Promise的含義: ??Promise是異步編程的一種解決方案挑宠,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和...
    呼呼哥閱讀 2,170評論 0 16
  • 1. Promise 的含義 所謂Promise菲盾,簡單說就是一個容器,里面保存著某個未來才會結(jié)束的事件(通常是一個...
    ROBIN2015閱讀 497評論 0 0
  • 前面的話 JS有很多強(qiáng)大的功能各淀,其中一個是它可以輕松地搞定異步編程懒鉴。作為一門為Web而生的語言,它從一開始就需要能...
    CodeMT閱讀 615評論 0 0