
接上一篇,继续介绍了 Promise 相关 API。
一、Promise.resolve()
Promise.resolve() 方法的作用就是将某个值(非 Promise 对象实例)转换为 Promise 对象实例。
const promise = Promise.resolve('foo')
// 相当于
const promise = new Promise(resolve => resolve('foo'))
Promise.resolve() 方法的参数分为四种情况:
1. 不带任何参数
它返回一个状态为 fulfilled,值为 undefined 的 Promise 实例对象。
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() 方法接受一个数组作为参数,其中 p1、p2、p3 都是 Promise 实例。如果数组中包含非 Promise 实例,它们会使用 Promise.resolve() 的方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all() 方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
其中 promise 的状态由 p1、p2、p3 决定,分为两种情况。
只有当
p1、p2、p3的状态都变成fulfilled,promise的状态才会变成fulfilled,此时p1、p2、p3实例的值,会组成一个数组,并传递给promise。只要
p1、p2、p3之中有一个被rejected,promise的状态就会变成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() 方法同样接受一个可迭代对象,只要 p1、p2、p3 中有一个实例率先改变状态(fulfiled 或 rejected),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 实例。只有等所有实例都返回结果(无论是 fulfilled 还 rejected),包装实例的状态才会发生变化。
我认为,这算是对 Promise.all() 存在 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.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)

列举以上示例,是为了得出以下结论:
Promise.allSettled()一定要等到参数中每一个Promise状态定型后,它返回的实例对象才会定型为fulfilled状态。否则只会是pending状态。类似
Promise.allSettled([])把一个空数组(空的迭代对象)作为参数,最后实例的状态为fulfilled,且实例的值为空数组[]。-
注意
Promise.allSettled()返回的实例的值,首先它是一个数组,而数组每项都是一个对象,该对象的属性取决于对应参数Promise实例的状态。例如
p1的状态为rejected,p2的状态为fulfilled。因此包装实例的前两项的对象分别为{ status: "rejected", reason: 1 }、{ status: "fulfilled", value: 2 },其他项同理。其中status属性只会是fulfilled或rejected两个字符串值。主要区别在于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)

当 Promise.any() 返回的实例变成 rejected 时,其实例的值是 AggregateError 实例。但传递一个空的迭代对象,Promise.any() 包装实例也会变成 rejected 状态,如 p5。
七、总结
关于 Promise.all()、Promise.race()、Promise.allSettled()、Promise.any() 方法,总结以下特点。
The end.









