細(xì)讀 ES6 | Promise 下篇

配圖源自 Freepik

上一篇,繼續(xù)介紹了 Promise 相關(guān) API。

一、Promise.resolve()

Promise.resolve() 方法的作用就是將某個(gè)值(非 Promise 對(duì)象實(shí)例)轉(zhuǎn)換為 Promise 對(duì)象實(shí)例。

const promise = Promise.resolve('foo')
// 相當(dāng)于
const promise = new Promise(resolve => resolve('foo'))

需要注意的是,它仍然會(huì)遵循 Event Loop 機(jī)制,包括后面介紹的其他 API。具體執(zhí)行順序本文不展開(kāi)討論。

Promise.resolve() 方法的參數(shù)分為四種情況:

1. 不帶任何參數(shù)

它返回一個(gè)狀態(tài)為 fulfilled,值為 undefinedPromise 實(shí)例對(duì)象。

const promise = Promise.resolve()

// promise 結(jié)果:
// {
//   [[Prototype]]: Promise,
//   [[PromiseState]]: "fulfilled",
//   [[PromiseResult]]: undefined
// }
2. 參數(shù)是一個(gè) Promise 實(shí)例對(duì)象

這時(shí),Promise.resolve() 將會(huì)不做任何修改、原封不動(dòng)地返回該實(shí)例。

請(qǐng)注意,即使參數(shù)是一個(gè) rejected 狀態(tài)的 Promise 實(shí)例,返回的實(shí)例也不會(huì)變成 fulfilled 狀態(tài),不要被這個(gè) 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' }
// }

其實(shí)這種情況,就是上一篇提到過(guò)的。

const p5 = new Promise(resolve => resovle(1))
const p6 = new Promise(resolve => {
  reslove(p5)
  // 注意,不要嘗試在此處調(diào)用 Promise.resolve(),會(huì)導(dǎo)致無(wú)限遞歸。
})

上面示例中,p6 的狀態(tài)取決于 p5 的狀態(tài)。

3. 參數(shù)是一個(gè) thenable 對(duì)象

thenable 對(duì)象,是指具有 then 方法的對(duì)象。例如:

const obj = {
  then: function(resolve, reject) {
    resolve('foo')
  }
}

上面示例中,obj 對(duì)象就是一個(gè) thenable 對(duì)象。Promise.resolve() 方法會(huì)將這個(gè) thenable 對(duì)象轉(zhuǎn)為 Promise 對(duì)象,然后就立即執(zhí)行 thenable 對(duì)象的 then() 方法。

const obj = {
  then: function (resolve, reject) {
    console.log(2)
    resolve('foo')
    // reject('foo') // 如果是這樣,最終 promise 對(duì)象將會(huì)變成了 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 對(duì)象的 then() 方法執(zhí)行后,對(duì)象 promise 的狀態(tài)變成了 fulfilled,接著執(zhí)行最后的那個(gè) promise.then() 方法,打印出 "foo"。

4. 參數(shù)是一個(gè)不具有 then() 方法的對(duì)象,或者壓根不是一個(gè)對(duì)象,而是原始值。

如果是這種情況,Promise.resolve() 方法返回一個(gè)新的 Promise 對(duì)象,狀態(tài)為 fulfilled,且該實(shí)例對(duì)象的值,就是該參數(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]]: {}
// }

在實(shí)際項(xiàng)目中,一般是第 4 種情況居多,我似乎真的沒(méi)見(jiàn)過(guò)前三種情況的。

二、Promise.reject()

Promise.reject() 方法會(huì)返回一個(gè)新的 Promise 實(shí)例對(duì)象,該實(shí)例的狀態(tài)總是為 rejected。

const promise = Promise.reject('foo')

// 相當(dāng)于
const promise = new Promise((resolve, reject) => reject('foo'))

Promise.resolve() 不同的是,Promise.reject() 方法的參數(shù)(無(wú)論是原始值、普通對(duì)象、還是 Promise 實(shí)例對(duì)象),將會(huì)原封不動(dòng)地作為返回實(shí)例對(duì)象的值。

Promise.reject('Oops').catch(err => {
  console.log(err === 'Oops') // true
  // do something...
})

三、Promise.all()

Promise.all() 方法用于將多個(gè) Promise 實(shí)例,包裝成一個(gè)新的 Promise 實(shí)例。

const promise = Promise.all([p1, p2, p3])

上面代碼中,Promise.all() 方法接受一個(gè)數(shù)組作為參數(shù),其中 p1、p2、p3 都是 Promise 實(shí)例。如果數(shù)組中包含非 Promise 實(shí)例,它們會(huì)使用 Promise.resolve() 的方法,將參數(shù)轉(zhuǎn)為 Promise 實(shí)例,再進(jìn)一步處理。另外,Promise.all() 方法的參數(shù)可以不是數(shù)組,但必須具有 Iterator 接口,且返回的每個(gè)成員都是 Promise 實(shí)例。

其中 promise 的狀態(tài)由 p1p2、p3 決定,分為兩種情況。

  • 只有當(dāng) p1p2、p3 的狀態(tài)都變成 fulfilled,promise 的狀態(tài)才會(huì)變成 fulfilled,此時(shí) p1p2、p3 實(shí)例的值,會(huì)組成一個(gè)數(shù)組,并傳遞給 promise。

  • 只要 p1p2、p3 之中有一個(gè)被 rejected,promise 的狀態(tài)就會(huì)變成 rejected。此時(shí)第一個(gè) rejected 實(shí)例的值(注意,不會(huì)像上面一樣組成數(shù)組哦),會(huì)傳遞給 promise。

看個(gè)例子:

const userIds = [1, 3, 5]
const promiseArr = userIds.map(id => {
  return window.fetch(`/config/user/${id}`) // 假設(shè)是請(qǐng)求用戶配置
})

Promise
  .all(promiseArr)
  .then(res => {
    // res 是一個(gè)數(shù)組,每一項(xiàng)對(duì)應(yīng)每個(gè)實(shí)例的值,即 [[PromiseResult]]
    // 常見(jiàn)做法是將 res 進(jìn)行解構(gòu),即 Promise.all(promiseArr).then(([a, b, c]) => { /* do something... */ })
    // 假設(shè) promiseArr 是一個(gè)空的可迭代對(duì)象,例如空數(shù)組,Promise.all([]) 實(shí)例狀態(tài)為 fulfilled,值為 []。
    // do something...
  })
  .catch(err => {
    // err 為 Promise.all() 被 rejected 的原因(reason)
  })

上面的示例中,promiseArr 是包含 3 個(gè) Promise 實(shí)例的數(shù)組,只有這 3 個(gè)實(shí)例的狀態(tài)都變成 fulfilled,或其中一個(gè)變?yōu)?rejected,才會(huì)調(diào)用 Promise.all() 方法的回調(diào)函數(shù)。

四、Promise.race()

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

const promise = Promise.race([p1, p2, p3])

Promise.race() 方法同樣接受一個(gè)可迭代對(duì)象,只要 p1、p2p3 中有一個(gè)實(shí)例率先改變狀態(tài)(fulfiledrejected),promise 的狀態(tài)就會(huì)跟著改變,而且 promise 實(shí)例的值就是率先改變的實(shí)例的返回值。若可迭代對(duì)象中的某一項(xiàng)不是 Promise 實(shí)例,仍會(huì)使用 Promise.resolve() 進(jìn)行轉(zhuǎn)換。

當(dāng)傳遞一個(gè)空的可迭代對(duì)象,那么 Promise.race() 實(shí)例的狀態(tài)將會(huì)一直停留在 pending。這點(diǎn)跟 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)引入的一個(gè)方法,同樣還是將多個(gè) Promise 實(shí)例包裝成一個(gè)新的 Promise 實(shí)例。只有等所有實(shí)例都返回結(jié)果(無(wú)論是 fulfilledrejected),包裝實(shí)例的狀態(tài)才會(huì)發(fā)生變化。

我認(rèn)為,這算是對(duì) Promise.all() 存在 rejected 實(shí)例情況的一種補(bǔ)全吧。

注意,Promise.allSettled() 的狀態(tài),只可能是 pendingfulfilled 狀態(tài),不可能存在 rejected 狀態(tài)。即只會(huì)從 pending -> fulfilled 的變化。

我們來(lái)看看以下示例,各種情況的結(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)將會(huì)一直停留在 pending

const p5 = Promise.allSettled([]) // 參數(shù)為空迭代對(duì)象
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 => {
    // 這里將不會(huì)執(zhí)行,因?yàn)?p6 一直處于 pending 狀態(tài)
    console.log('p6 then:', res)
  })
  p7.then(res => {
    console.log('p7 then:', res)
  })
}, 2000)

列舉以上示例,是為了得出以下結(jié)論:

  • Promise.allSettled() 一定要等到參數(shù)中每一個(gè) Promise 狀態(tài)定型后,它返回的實(shí)例對(duì)象才會(huì)定型為 fulfilled 狀態(tài)。否則只會(huì)是 pending 狀態(tài)。

  • 類(lèi)似 Promise.allSettled([]) 把一個(gè)空數(shù)組(空的迭代對(duì)象)作為參數(shù),最后實(shí)例的狀態(tài)為 fulfilled,且實(shí)例的值為空數(shù)組 []。

  • 注意 Promise.allSettled() 返回的實(shí)例的值,首先它是一個(gè)數(shù)組,而數(shù)組每項(xiàng)都是一個(gè)對(duì)象,該對(duì)象的屬性取決于對(duì)應(yīng)參數(shù) Promise 實(shí)例的狀態(tài)。

    例如 p1 的狀態(tài)為 rejectedp2 的狀態(tài)為 fulfilled。因此包裝實(shí)例的前兩項(xiàng)的對(duì)象分別為 { status: "rejected", reason: 1 }{ status: "fulfilled", value: 2 },其他項(xiàng)同理。其中 status 屬性只會(huì)是 fulfilledrejected 兩個(gè)字符串值。主要區(qū)別在于 value 屬性和 reason 屬性,即 fulfilled 狀態(tài)對(duì)應(yīng) value 屬性,而 rejected 狀態(tài)對(duì)應(yīng) reason 屬性。

有時(shí)候,我們不關(guān)心異步操作的結(jié)果,只關(guān)心這些操作有沒(méi)有結(jié)束。這時(shí),使用 Promise.allSettled() 方法就很有用了。而 Promise.all() 是沒(méi)辦法確保這一點(diǎn)的。

六、Promise.any()

在 ES12 標(biāo)準(zhǔn)中,引入了 Promise.any() 方法,它用于將多個(gè) Promise 實(shí)例,包裝成一個(gè)新的 Promise 實(shí)例。

Promise.any() 接受一個(gè) Promise 可迭代對(duì)象,只要參數(shù)實(shí)例中有一個(gè)變成 fulfilled 狀態(tài),包裝實(shí)例就會(huì)變成 fulfilled 狀態(tài),其值就是參數(shù)實(shí)例的值。

Promise.any()Promise.race() 很像,只有一個(gè)不同點(diǎn),就是 Promise.any() 不會(huì)因?yàn)槟硞€(gè)參數(shù) Promise 實(shí)例變成 rejected 狀態(tài)而接受,必須要等到所有參數(shù)實(shí)例的狀態(tài)都變?yōu)?rejected,包裝實(shí)例的狀態(tài)才會(huì)是 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)將會(huì)一直停留在 pending

const p5 = Promise.any([]) // p5 會(huì)變成 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)會(huì)變成 rejected,因此會(huì)執(zhí)行到這里。
    console.log('p5 catch:', err)
  })
  p6.then(res => {
    // p6 的狀態(tài)一直會(huì)是 pending,因此不會(huì)執(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 是一個(gè)對(duì)象
    console.log('p8 catch:', err)
    console.dir(err)
  })
}, 2000)

當(dāng) Promise.any() 返回的實(shí)例變成 rejected 時(shí),其實(shí)例的值是 AggregateError 實(shí)例。但傳遞一個(gè)空的迭代對(duì)象,Promise.any() 包裝實(shí)例也會(huì)變成 rejected 狀態(tài),如 p5

七、總結(jié)

關(guān)于 Promise.all()、Promise.race()、Promise.allSettled()、Promise.any() 方法,總結(jié)以下特點(diǎn)。

  • 它們的用處都是將多個(gè) Promise 實(shí)例,包裝成一個(gè)新的 Promise 實(shí)例。

  • 它們都接受一個(gè)具有 Iterator 接口的可迭代對(duì)象,通常為數(shù)組。且會(huì)返回一個(gè)新的 Promise 實(shí)例對(duì)象。

  • 它們處理參數(shù)為空的可迭代對(duì)象的方式不一樣,本來(lái)就是要處理多個(gè) Promise 對(duì)象,才會(huì)用到它們,所以這種情況無(wú)需理會(huì)。真遇到再回來(lái)翻閱文檔即可,現(xiàn)在我寫(xiě)到這里都記不太清楚其中的區(qū)別了,但問(wèn)題不大。

  • Promise.all() 當(dāng)所有實(shí)例均為 fulfilled 狀態(tài),最終的包裝實(shí)例才會(huì)是 fulfilled,其值是一個(gè)數(shù)組。否則將會(huì)是 rejected 狀態(tài);

    Promise.race() 則是某個(gè)實(shí)例的狀態(tài)發(fā)生變化,最終包裝實(shí)例將對(duì)應(yīng)率先變化實(shí)例所對(duì)應(yīng)的值和狀態(tài)?!鞍l(fā)生變化”是指 pending -> fulfilledpending -> rejected。

    Promise.allSettled() 單從命名上來(lái)猜測(cè),就知道它需要等所有參數(shù)實(shí)例確定狀態(tài)后,包裝實(shí)例的狀態(tài)才會(huì)變成 fulfilled 狀態(tài),注意它不存在 rejected 狀態(tài)的情況。包裝實(shí)例的返回值是一個(gè)數(shù)組,數(shù)組每項(xiàng)可能是 { status: "fulfilled", value: /* 對(duì)應(yīng) fulfilled 的值 */ }{ status: "rejected", reason: /* 對(duì)應(yīng) rejected 的原因 */ },取決于每個(gè)參數(shù)實(shí)例的狀態(tài)。

    Promise.any() 當(dāng)某個(gè)參數(shù)實(shí)例的狀態(tài)變?yōu)?fulfilled,那么包裝實(shí)例就定型了,對(duì)應(yīng)該參數(shù)實(shí)例的狀態(tài)和值。否則它必須等到所有參數(shù)實(shí)例變?yōu)?rejected 狀態(tài),包裝實(shí)例的狀態(tài)才會(huì)發(fā)生改變,變?yōu)?rejected,其值是一個(gè) AggregateError 實(shí)例。

The end.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Promise含義 Promise是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更強(qiáng)大。所謂Pr...
    oWSQo閱讀 1,133評(píng)論 0 4
  • Promise 含義 Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)...
    Upcccz閱讀 291評(píng)論 0 0
  • Promise的含義: ??Promise是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和...
    呼呼哥閱讀 2,270評(píng)論 0 16
  • 1. Promise 的含義 所謂Promise,簡(jiǎn)單說(shuō)就是一個(gè)容器,里面保存著某個(gè)未來(lái)才會(huì)結(jié)束的事件(通常是一個(gè)...
    ROBIN2015閱讀 581評(píng)論 0 0
  • 前面的話 JS有很多強(qiáng)大的功能,其中一個(gè)是它可以輕松地搞定異步編程。作為一門(mén)為Web而生的語(yǔ)言,它從一開(kāi)始就需要能...
    CodeMT閱讀 709評(píng)論 0 0

友情鏈接更多精彩內(nèi)容