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