Promise
类中有 6 个静态方法。我们在这里快速介绍一下它们的用例。
Promise.all
假设我们希望并行执行多个 Promise,并等到所有 Promise 都准备就绪。
例如,并行下载多个 URL,并在所有下载完成后处理内容。
这就是 Promise.all
的作用。
语法为
let promise = Promise.all(iterable);
Promise.all
接受一个可迭代对象(通常是承诺数组),并返回一个新的承诺。
当列出的所有承诺都得到解决时,新的承诺就会得到解决,并且它们的结果数组将成为其结果。
例如,下面的 Promise.all
在 3 秒后解决,然后其结果是一个数组 [1, 2, 3]
Promise.all([
new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3
]).then(alert); // 1,2,3 when promises are ready: each promise contributes an array member
请注意,结果数组成员的顺序与其源承诺中的顺序相同。即使第一个承诺花费最长时间才能解决,它仍然是结果数组中的第一个。
一个常见的技巧是将作业数据数组映射到承诺数组,然后将其包装到 Promise.all
中。
例如,如果我们有一个 URL 数组,我们可以像这样获取它们
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];
// map every url to the promise of the fetch
let requests = urls.map(url => fetch(url));
// Promise.all waits until all jobs are resolved
Promise.all(requests)
.then(responses => responses.forEach(
response => alert(`${response.url}: ${response.status}`)
));
一个更大的示例,通过名称获取 GitHub 用户数组的用户信息(我们可以通过其 ID 获取商品数组,逻辑是相同的)
let names = ['iliakan', 'remy', 'jeresig'];
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));
Promise.all(requests)
.then(responses => {
// all responses are resolved successfully
for(let response of responses) {
alert(`${response.url}: ${response.status}`); // shows 200 for every url
}
return responses;
})
// map array of responses into an array of response.json() to read their content
.then(responses => Promise.all(responses.map(r => r.json())))
// all JSON answers are parsed: "users" is the array of them
.then(users => users.forEach(user => alert(user.name)));
如果任何承诺被拒绝,则 Promise.all
返回的承诺会立即拒绝该错误。
例如
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(alert); // Error: Whoops!
此处第二个承诺在两秒内拒绝。这会导致 Promise.all
立即拒绝,因此执行 .catch
:拒绝错误成为整个 Promise.all
的结果。
如果一个承诺被拒绝,则 Promise.all
立即拒绝,完全忘记列表中的其他承诺。它们的结果被忽略。
例如,如果有多个 fetch
调用,如上面的示例所示,并且一个调用失败,则其他调用仍将继续执行,但 Promise.all
将不再监视它们。它们可能会解决,但它们的结果将被忽略。
Promise.all
不会做任何事情来取消它们,因为在承诺中没有“取消”的概念。在 另一章 中,我们将介绍 AbortController
,它可以帮助解决此问题,但它不是 Promise API 的一部分。
Promise.all(iterable)
允许在 iterable
中使用非承诺的“常规”值通常,Promise.all(...)
接受一个可迭代对象(大多数情况下为数组)的 Promise。但是,如果其中任何对象不是 Promise,它将“按原样”传递给结果数组。
例如,这里的结果是 [1, 2, 3]
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000)
}),
2,
3
]).then(alert); // 1, 2, 3
因此,我们可以将就绪值传递给 Promise.all
,以便于使用。
Promise.allSettled
如果任何 Promise 被拒绝,Promise.all
将整体拒绝。这适用于“全有或全无”的情况,即我们需要所有结果都成功才能继续进行
Promise.all([
fetch('/template.html'),
fetch('/style.css'),
fetch('/data.json')
]).then(render); // render method needs results of all fetches
Promise.allSettled
只是等待所有 Promise 完成,无论结果如何。结果数组具有
{status:"fulfilled", value:result}
表示成功的响应,{status:"rejected", reason:error}
表示错误。
例如,我们希望获取多个用户的信息。即使一个请求失败,我们仍然对其他请求感兴趣。
让我们使用 Promise.allSettled
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://no-such-url'
];
Promise.allSettled(urls.map(url => fetch(url)))
.then(results => { // (*)
results.forEach((result, num) => {
if (result.status == "fulfilled") {
alert(`${urls[num]}: ${result.value.status}`);
}
if (result.status == "rejected") {
alert(`${urls[num]}: ${result.reason}`);
}
});
});
上面 (*)
行中的 results
将是
[
{status: 'fulfilled', value: ...response...},
{status: 'fulfilled', value: ...response...},
{status: 'rejected', reason: ...error object...}
]
因此,对于每个 Promise,我们都会获取其状态和 value/error
。
Polyfill
如果浏览器不支持 Promise.allSettled
,则很容易进行 polyfill
if (!Promise.allSettled) {
const rejectHandler = reason => ({ status: 'rejected', reason });
const resolveHandler = value => ({ status: 'fulfilled', value });
Promise.allSettled = function (promises) {
const convertedPromises = promises.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler));
return Promise.all(convertedPromises);
};
}
在此代码中,promises.map
采用输入值,使用 p => Promise.resolve(p)
将它们转换为 Promise(以防传递了非 Promise),然后向每个值添加 .then
处理程序。
该处理程序将成功的结果 value
转换为 {status:'fulfilled', value}
,并将错误 reason
转换为 {status:'rejected', reason}
。这正是 Promise.allSettled
的格式。
现在,我们可以使用 Promise.allSettled
来获取所有给定 Promise 的结果,即使其中一些 Promise 被拒绝。
Promise.race
类似于 Promise.all
,但只等待第一个已完成的 Promise 并获取其结果(或错误)。
语法为
let promise = Promise.race(iterable);
例如,这里的结果将是 1
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
这里第一个 Promise 最快,因此它成为结果。在第一个已完成的 Promise “赢得比赛”后,所有进一步的结果/错误都将被忽略。
Promise.any
类似于 Promise.race
,但仅等待第一个已完成的 Promise 并获取其结果。如果给定的所有 Promise 都被拒绝,则返回的 Promise 将被拒绝,并带有 AggregateError
,这是一个特殊的错误对象,它将所有 Promise 错误存储在它的 errors
属性中。
语法为
let promise = Promise.any(iterable);
例如,这里的结果将是 1
Promise.any([
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 1000)),
new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
这里的第一个 Promise 最快,但它被拒绝了,所以第二个 Promise 成为结果。在第一个已完成的 Promise “赢得比赛”后,所有进一步的结果都将被忽略。
下面是一个所有 Promise 都失败的示例
Promise.any([
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ouch!")), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Error!")), 2000))
]).catch(error => {
console.log(error.constructor.name); // AggregateError
console.log(error.errors[0]); // Error: Ouch!
console.log(error.errors[1]); // Error: Error!
});
如你所见,失败 Promise 的错误对象在 AggregateError
对象的 errors
属性中可用。
Promise.resolve/reject
在现代代码中很少需要 Promise.resolve
和 Promise.reject
方法,因为 async/await
语法(我们将在 稍后介绍)使它们在某种程度上过时了。
我们在这里介绍它们是为了完整性,以及出于某种原因无法使用 async/await
的人。
Promise.resolve
Promise.resolve(value)
创建一个已解决的 Promise,其结果为 value
。
与以下相同
let promise = new Promise(resolve => resolve(value));
当期望函数返回 Promise 时,该方法用于兼容性。
例如,下面的 loadCached
函数获取一个 URL 并记住(缓存)其内容。对于使用相同 URL 的未来调用,它会立即从缓存中获取以前的内容,但使用 Promise.resolve
来生成它的 Promise,因此返回的值始终是一个 Promise
let cache = new Map();
function loadCached(url) {
if (cache.has(url)) {
return Promise.resolve(cache.get(url)); // (*)
}
return fetch(url)
.then(response => response.text())
.then(text => {
cache.set(url,text);
return text;
});
}
我们可以编写 loadCached(url).then(…)
,因为该函数保证返回一个 Promise。我们始终可以在 loadCached
后使用 .then
。这就是 Promise.resolve
在行 (*)
中的用途。
Promise.reject
Promise.reject(error)
创建一个被 error
拒绝的 Promise。
与以下相同
let promise = new Promise((resolve, reject) => reject(error));
在实践中,几乎从不使用此方法。
摘要
Promise
类有 6 个静态方法
Promise.all(promises)
– 等待所有 Promise 解析并返回其结果的数组。如果给定的任何 Promise 被拒绝,它将成为Promise.all
的错误,所有其他结果都将被忽略。Promise.allSettled(promises)
(最近添加的方法)– 等待所有 Promise 稳定并返回其结果,作为带有以下内容的对象数组状态
:“已完成”
或“已拒绝”
值
(如果已完成)或原因
(如果已拒绝)。
Promise.race(promises)
– 等待第一个承诺解决,其结果/错误将成为结果。Promise.any(promises)
(最近添加的方法) – 等待第一个承诺实现,其结果将成为结果。如果所有给定的承诺都被拒绝,则AggregateError
将成为Promise.any
的错误。Promise.resolve(value)
– 使用给定值创建一个已解决的承诺。Promise.reject(error)
– 使用给定的错误创建一个已拒绝的承诺。
在所有这些中,Promise.all
可能是实践中最常见的。
评论
<code>
标记,对于多行 – 将它们包装在<pre>
标记中,对于 10 行以上 – 使用沙箱(plnkr,jsbin,codepen…)