细读 ES6 | Promise 下篇

简介: 继续介绍 Promise 相关 API。

前言


继续介绍 Promise 相关 API。


正文


一、Promise.resolve()


Promise.resolve() 方法的作用就是将某个值(非 Promise 对象实例)转换为 Promise 对象实例。

const promise = Promise.resolve('foo')
// 相当于
const promise = new Promise(resolve => resolve('foo'))


需要注意的是,它仍然会遵循 Event Loop 机制,包括后面介绍的其他 API。具体执行顺序本文不展开讨论。


Promise.resolve() 方法的参数分为四种情况:


1. 不带任何参数


它返回一个状态为 fulfilled,值为 undefinedPromise 实例对象。

const promise = Promise.resolve()
// promise 结果:
// {
//   [[Prototype]]: Promise,
//   [[PromiseState]]: "fulfilled",
//   [[PromiseResult]]: undefined
// }


2. 参数是一个 Promise 实例对象


这时,Promise.resolve() 将会不做任何修改、原封不动地返回该实例。

请注意,即使参数是一个 rejected 状态的 Promise 实例,返回的实例也不会变成 fulfilled 状态,不要被这个 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 结果:
// {
//   [[Prototype]]: Promise,
//   [[PromiseState]]: "fulfilled",
//   [[PromiseResult]]: { name: 'Frankie' }
// }
// p4 结果:
// {
//   [[Prototype]]: Promise,
//   [[PromiseState]]: "rejected",
//   [[PromiseResult]]: { name: 'Frankie' }
// }

const p5 = new Promise(resolve => resovle(1))
const p6 = new Promise(resolve => {
  reslove(p5)
  // 注意,不要尝试在此处调用 Promise.resolve(),会导致无限递归。
})


上面示例中,p6 的状态取决于 p5 的状态。


3. 参数是一个 thenable 对象


thenable 对象,是指具有 then 方法的对象。例如:

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


上面示例中,obj 对象就是一个 thenable 对象。Promise.resolve() 方法会将这个 thenable 对象转为 Promise 对象,然后就立即执行 thenable 对象的 then() 方法。

const obj = {
  then: function (resolve, reject) {
    console.log(2)
    resolve('foo')
    // reject('foo') // 如果是这样,最终 promise 对象将会变成了 rejected 状态。
  }
}
const promise = Promise.resolve(obj)
promise.then(res => {
  console.log(3)
  console.log(res) // "foo"
})
console.log(1)
// 打印结果分别是 1、2、3、"foo"
// promise 结果:
// {
//   [[Prototype]]: Promise,
//   [[PromiseState]]: "fulfilled",
//   [[PromiseResult]]: "foo"
// }


上述示例中,obj 对象的 then() 方法执行后,对象 promise 的状态变成了 fulfilled,接着执行最后的那个 promise.then() 方法,打印出 "foo"


4. 参数是一个不具有 then() 方法的对象,或者压根不是一个对象,而是原始值。


如果是这种情况,Promise.resolve() 方法返回一个新的 Promise 对象,状态为 fulfilled,且该实例对象的值,就是该参数值。

const p1 = Promise.resolve('foo')
const p2 = Promise.resolve({})
// p1 结果:
// {
//   [[Prototype]]: Promise,
//   [[PromiseState]]: "fulfilled",
//   [[PromiseResult]]: "foo"
// }
// p2 结果:
// {
//   [[Prototype]]: Promise,
//   [[PromiseState]]: "fulfilled",
//   [[PromiseResult]]: {}
// }


在实际项目中,一般是第 4 种情况居多,我似乎真的没见过前三种情况的。


二、Promise.reject()


Promise.reject() 方法会返回一个新的 Promise 实例对象,该实例的状态总是为 rejected

const promise = Promise.reject('foo')
// 相当于
const promise = new Promise((resolve, reject) => reject('foo'))

Promise.resolve() 不同的是,Promise.reject() 方法的参数(无论是原始值、普通对象、还是 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() 方法接受一个数组作为参数,其中 p1p2p3 都是 Promise 实例。如果数组中包含非 Promise 实例,它们会使用 Promise.resolve() 的方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all() 方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

其中 promise 的状态由 p1p2p3 决定,分为两种情况。

  • 只有当 p1p2p3 的状态都变成 fulfilledpromise 的状态才会变成 fulfilled,此时 p1p2p3 实例的值,会组成一个数组,并传递给 promise
  • 只要  p1p2p3  之中有一个被 rejectedpromise 的状态就会变成 rejected。此时第一个 rejected 实例的值(注意,不会像上面一样组成数组哦),会传递给 promise


看个例子:

const userIds = [1, 3, 5]
const promiseArr = userIds.map(id => {
  return window.fetch(`/config/user/${id}`) // 假设是请求用户配置
})
Promise
  .all(promiseArr)
  .then(res => {
    // res 是一个数组,每一项对应每个实例的值,即 [[PromiseResult]]
    // 常见做法是将 res 进行解构,即 Promise.all(promiseArr).then(([a, b, c]) => { /* do something... */ })
    // 假设 promiseArr 是一个空的可迭代对象,例如空数组,Promise.all([]) 实例状态为 fulfilled,值为 []。
    // do something...
  })
  .catch(err => {
    // err 为 Promise.all() 被 rejected 的原因(reason)
  })


上面的示例中,promiseArr 是包含 3 个 Promise 实例的数组,只有这 3 个实例的状态都变成 fulfilled,或其中一个变为 rejected,才会调用 Promise.all() 方法的回调函数。


四、Promise.race()


Promise.race() 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

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


Promise.race() 方法同样接受一个可迭代对象,只要  p1p2p3  中有一个实例率先改变状态(fulfiledrejected),promise 的状态就会跟着改变,而且 promise 实例的值就是率先改变的实例的返回值。若可迭代对象中的某一项不是 Promise 实例,仍会使用 Promise.resolve() 进行转换。

当传递一个空的可迭代对象,那么 Promise.race() 实例的状态将会一直停留在 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 标准引入的一个方法,同样还是将多个 Promise 实例包装成一个新的 Promise 实例。只有等所有实例都返回结果(无论是 fulfilledrejected),包装实例的状态才会发生变化。


我认为,这算是对 Promise.all() 存在 rejected 实例情况的一种补全吧。


注意,Promise.allSettled() 的状态,只可能是 pendingfulfilled 状态,不可能存在 rejected 状态。即只会从 pending -> fulfilled 的变化。


我们来看看以下示例,各种情况的结果吧:

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 状态将会一直停留在 pending
const p5 = Promise.allSettled([]) // 参数为空迭代对象
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 => {
    // 这里将不会执行,因为 p6 一直处于 pending 状态
    console.log('p6 then:', res)
  })
  p7.then(res => {
    console.log('p7 then:', res)
  })
}, 2000)


2.webp.jpg


列举以上示例,是为了得出以下结论:


  • Promise.allSettled() 一定要等到参数中每一个 Promise 状态定型后,它返回的实例对象才会定型为 fulfilled 状态。否则只会是 pending 状态。
  • 类似 Promise.allSettled([]) 把一个空数组(空的迭代对象)作为参数,最后实例的状态为 fulfilled,且实例的值为空数组 []
  • 注意 Promise.allSettled() 返回的实例的值,首先它是一个数组,而数组每项都是一个对象,该对象的属性取决于对应参数 Promise 实例的状态。
    例如 p1 的状态为 rejectedp2 的状态为 fulfilled。因此包装实例的前两项的对象分别为 { status: "rejected", reason: 1 }{ status: "fulfilled", value: 2 },其他项同理。其中 status 属性只会是 fulfilledrejected 两个字符串值。主要区别在于 value 属性和 reason 属性,即 fulfilled 状态对应 value 属性,而 rejected 状态对应 reason 属性。


有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,使用 Promise.allSettled() 方法就很有用了。而 Promise.all() 是没办法确保这一点的。


六、Promise.any()


在 ES12 标准中,引入了 Promise.any() 方法,它用于将多个 Promise 实例,包装成一个新的 Promise 实例。


Promise.any() 接受一个 Promise 可迭代对象,只要参数实例中有一个变成 fulfilled 状态,包装实例就会变成 fulfilled 状态,其值就是参数实例的值。

Promise.any()Promise.race() 很像,只有一个不同点,就是 Promise.any() 不会因为某个参数 Promise 实例变成 rejected 状态而接受,必须要等到所有参数实例的状态都变为 rejected,包装实例的状态才会是 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 状态将会一直停留在 pending
const p5 = Promise.any([]) // p5 会变成 rejected 状态
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 的状态会变成 rejected,因此会执行到这里。
    console.log('p5 catch:', err)
  })
  p6.then(res => {
    // p6 的状态一直会是 pending,因此不会执行回调。
    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)


0.webp.jpg


Promise.any() 返回的实例变成 rejected 时,其实例的值是 AggregateError 实例。但传递一个空的迭代对象,Promise.any() 包装实例也会变成 rejected 状态,如 p5


七、总结


关于 Promise.all()Promise.race()Promise.allSettled()Promise.any() 方法,总结以下特点。

  • 它们的用处都是将多个 Promise 实例,包装成一个新的 Promise 实例。
  • 它们都接受一个具有 Iterator 接口的可迭代对象,通常为数组。且会返回一个新的 Promise 实例对象。
  • 它们处理参数为空的可迭代对象的方式不一样,本来就是要处理多个 Promise 对象,才会用到它们,所以这种情况无需理会。真遇到再回来翻阅文档即可,现在我写到这里都记不太清楚其中的区别了,但问题不大。
  • Promise.all() 当所有实例均为 fulfilled 状态,最终的包装实例才会是 fulfilled,其值是一个数组。否则将会是 rejected 状态;
    Promise.race() 则是某个实例的状态发生变化,最终包装实例将对应率先变化实例所对应的值和状态。“发生变化”是指 pending -> fulfilledpending -> rejected
    Promise.allSettled() 单从命名上来猜测,就知道它需要等所有参数实例确定状态后,包装实例的状态才会变成 fulfilled 状态,注意它不存在 rejected 状态的情况。包装实例的返回值是一个数组,数组每项可能是 { status: "fulfilled", value: /* 对应 fulfilled 的值 */ }{ status: "rejected", reason: /* 对应 rejected 的原因 */ },取决于每个参数实例的状态。
    Promise.any() 当某个参数实例的状态变为 fulfilled,那么包装实例就定型了,对应该参数实例的状态和值。否则它必须等到所有参数实例变为 rejected 状态,包装实例的状态才会发生改变,变为 rejected,其值是一个 AggregateError 实例。


The end.


目录
相关文章
|
21天前
|
前端开发
理解 ES6 中的 Promise
【10月更文挑战第24天】ES6 中的 Promise 是一种用于处理异步操作的机制,它提供了一种更优雅、更可控的方式来处理异步任务的结果。Promise 可以看作是对异步操作结果的一种承诺,它可以处于三种不同的状态:Pending(等待中)、Fulfilled(已完成,即成功)和 Rejected(已拒绝,即失败)。
|
1月前
|
前端开发 JavaScript 小程序
JavaScript的ES6中Promise的使用以及个人理解
JavaScript的ES6中Promise的使用以及个人理解
18 1
|
1月前
|
前端开发 Java
说说你对es6中promise的理解?
说说你对es6中promise的理解?
15 1
|
1月前
|
前端开发 Java
说说你对es6中promise的理解?
说说你对es6中promise的理解?
|
2月前
|
前端开发 JavaScript
ES6新标准下JS异步编程Promise解读
ES6新标准下JS异步编程Promise解读
36 3
|
3月前
|
前端开发
手写实现ES6的Promise.all()和Promise.race()函数
这篇文章介绍了如何手写实现ES6的`Promise.all()`和`Promise.race()`函数,提供了实现这两个Promise聚合函数的详细代码示例,并展示了如何使用它们。
手写实现ES6的Promise.all()和Promise.race()函数
|
1月前
|
存储 前端开发 JavaScript
关于 ES6 中 Promise 的面试题
关于 ES6 中 Promise 的面试题
16 0
|
6月前
|
前端开发 JavaScript
如何处理 JavaScript 中的异步操作和 Promise?
如何处理 JavaScript 中的异步操作和 Promise?
66 1
|
6月前
|
前端开发 JavaScript
在JavaScript中,什么是promise、怎么使用promise、怎么手写promise
在JavaScript中,什么是promise、怎么使用promise、怎么手写promise
99 4
|
6月前
|
前端开发 JavaScript 开发者
JavaScript 中的异步编程:Promise 和 Async/Await
在现代的 JavaScript 开发中,异步编程是至关重要的。本文将介绍 JavaScript 中的异步编程概念,重点讨论 Promise 和 Async/Await 这两种常见的处理异步操作的方法。通过本文的阐述,读者将能够更好地理解和应用这些技术,提高自己在 JavaScript 开发中处理异步任务的能力。

热门文章

最新文章

下一篇
无影云桌面