细读 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
实例。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论