五、Promise 模式
原生 ES6 Promise 支持了两个模式:Promise.all([ .. ])
和 Promise.race([ .. ])
(ES2020 新增了一个模式:Promise.allSettled([ .. ])
)。
5.1 Promise.all([ .. ])
在异步序列中(Promise 链),任意时刻都只能有一个异步任务正在执行——步骤 2 只能在步骤 1 之后,步骤 3 只能在步骤 2 之后。但是,如果想要同时执行两个或更多步骤(也就是“并行执行”),要怎么实现呢?
在经典的编程术语中,门是这样一种机制:要等待两个或更多并行 / 并发的任务都完成才能继续。它们的完成顺序并不重要,但是必须都要完成,门才能打开并让流程控制继续。
在 Promise API 中,这种模式被称为 all([ .. ])
。
假定你想要同时发送两个 Ajax 请求,等它们不管以什么顺序全部完成之后,再发送第三个 Ajax 请求。示例:
// request 是一个前面定义的工具函数
let p1 = request('http://some.url.1')
let p2 = request('http://some.url.2')
Promise.all([p1, p2])
.then(res => {
// p1 和 p2 完成并把它们的消息传入
return request(`http://some.url.3?v=${res.join(',')}`)
})
.then(res => {
console.log(res)
})
Promise.all([ .. ])
需要一个参数,是一个数组,通常由 Promise 实例组成。从 Promise.all([ .. ])
调用返回的 promise
会收到一个完成消息。这是一个由所有传入 promise
的完成消息组成的数组,与指定的顺序一致(与完成顺序无关)。
说明:传给 Promise.all([ .. ])
的数组中的值可以是 Promise、thenable,甚至是立即值。列表中的每个值都会通过 Promise.resolve(..)
过滤,以确保要等待的是一个真正的 Promise。如果数组是空的,主 Promise 就会立即完成。
从 Promise.all([ .. ])
返回的主 promise
在且仅在所有的成员 promise
都完成后才会完成。如果这些 promise
中有任何一个被拒绝的话,主 Promise.all([ .. ])
返回的 promise
就会立即被拒绝,并丢弃来自其他所有 promise
的全部结果。
5.2 Promise.race([ .. ])
尽管 Promise.all([ .. ])
协调多个并发 Promise 的运行,并假定所有 Promise 都需要完成,但有时候你会想只响应“第一个跨过终点线的 Promise”,而抛弃其他 Promise。
这种模式传统上称为门闩(shuan),但在 Promise 中称为竞态。
Promise.race([ .. ])
也接受单个数组参数。这个数组由一个或多个 Promise、thenable 或立即值组成。立即值之间的竞争在实践中没有太大意义,因为显然列表中的第一个会获胜。
与 Promise.all([ .. ])
类似,一旦有任何一个 Promise 决议为完成,Promise.race([ .. ])
就会完成;一旦有任何一个 Promise 决议为拒绝,它就会拒绝。
说明:如果你传入了一个空数组,主 race([..])
Promise 永远不会决议,而不是立即决议。
因为只有一个 promise
能够取胜,所以完成值是单个消息,而不是像对 Promise.all([ .. ])
那样的是一个数组。
5.3 Promise.allSettled([ .. ])
Promise.allSettled([ .. ])
接受单个 Promise 实例组成的数组参数,同上面两个模式类似。
从 Promise.allSettled([ .. ])
返回的主 promise
在且仅在所有的成员 promise
都返回结果(不管是完成还是拒绝)后才会完成。
该方法返回的主 promise
,一旦结束,状态总是 fulfilled
,不会变成 rejected
。
示例:
const resolved = Promise.resolve(24)
const rejected = Promise.reject('err')
Promise.allSettled([resolved, rejected])
.then(res => {
console.log(res)
// [
// { status: 'fulfilled', value: 24},
// { status: 'rejected', reason: 'err'}
// ]
})
Promise.allSettled([ .. ])
返回的主 Promise 接受到的返回参数是一个数组,每个成员对应一个传入 Promise.allSettled()
的 Promise 实例,与指定的顺序一致。
返回参数数组中每个成员都是一个对象,每个对象都有 status
属性,该属性的值只可能是字符串 fulfilled
和 rejected
。值是 fulfilled
时,对象有 value
属性,值是 rejected
时,有 reason
属性,存放两种状态的返回值。
有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,Promise.allSettled([ .. ])
模式就很有用。Promise.all([ .. ])
方法无法做到这一点。
5.4 all([ .. ]) 和 race([ .. ]) 的变体
none([ .. ])
:这个模式类似于all([ .. ])
,不过完成和拒绝的情况互换了。所有的 Promise 都要被拒绝。any([ .. ])
:这个模式与all([ .. ])
类似,但是会忽略拒绝,所以只需要完成一个而不是全部。first([ .. ])
:这个模式类似于与any([ .. ])
的竞争,即只要第一个 Promise 完成,它就会忽略后续的任何拒绝和完成。last([ .. ])
:这个模式类似于first([ .. ])
,但却是只有最后一个完成胜出。
原生 ES6 Promise 并没有实现这些模式,我们可以用 Promise
、race([ .. ])
和 all([ .. ])
这些机制,来自己实现它们。
例如 first([ .. ])
:
if (!Promise.first) {
Promise.first = prs => {
return new Promise((resolve, reject) => {
// 循环所有的 promise
prs.forEach(pr => {
// 规范化 Promise
Promise.resolve(pr)
// 不管哪个最先完成,就决议主 promise
.then(resolve)
})
})
}
}
参考资料
- 《你不知道的JavaScript(中卷)》
- ES6教程(阮一峰)