原文链接:http://javascript.info/promise-api,translate with ❤️ by zhangbao.
在 Promise 上存在 4 个静态方法。本章将快速介绍它们的使用。
Promise.resolve
语法是:
let promise = Promise.resolve(value);
使用给定的参数 value,返回 resolved 状态的 Promise。
等同于:
let promise = new Promise(resolve => resolve(value));
是在已知值的情况下使用这个方法,把它“包装”到 Promise 中。
例如,下面的 loadCached 函数 fetch url 并且记住结果,这样在未来某个时刻再调用同一个 URL 时会立即返回结果。
function loadCached(url) {let cache = loadCached.cache || (loadCached.cache = new Map());if (cache.has(url)) {return Promise.resolve(cache.get(url)); // (*)}return fetch(url).then(response => response.text()).then(text => {cache[url] = text;return text;});}
我们可以使用 loadCached(url).then(...),因为函数始终返回一个 Promise。在 (*) 处 Primise.resolve() 的目的是:保证接口是统一的,我们可以在 loadCached 之后使用 .then。
Promise.reject
语法是:
let promise = Promise.reject(error);
使用给定的错误参数 error,创建一个 rejected 状态的 Promise 对象。
等同于:
let promise = new Promise((resolve, reject) => reject(error));
我们是为了完整性才在这里介绍它,在实际代码里很少使用它。
Promise.all
这个方法用来平行地同时执行多个 Promise,一直等到所有的 Promise 都解决了。
语法是:
let promise = Promise.all(iterable);
它接收一个由 Promise 对象组成的 iterable 对象(技术上讲——可以是任意的可迭代对象,但是通常是一个数组),方法返回一个新的 Promise 对象。当所有的 Promise 都解决并产生一系列结果时,新的 Promise 就 resolved 了。
例如,下面的 Primise.all 在 3 秒后解决,它的结果是是一个数组 [1, 2, 3]。
Promise.all([new Promise((resolve, reject) => setTimeout(() => resolve(1), 3000)), // 1new Promise((resolve, reject) => setTimeout(() => resolve(2), 2000)), // 2new Promise((resolve, reject) => setTimeout(() => resolve(3), 1000)) // 3]).then(alert); // 1,2,3 当所有的 Promise 都解决了——每个 Promise 都贡献了一个数组元素。
请注意,结果顺序和请求顺序是一致的。尽管第一个 Promise 需要花费最长的时间来解决,但它仍然是返回结果中的第一个元素。
一个常见技巧是将一组任合并到由 Promise 对象组成的数组中,然后将其包装到 Promise.all 中。
例如,我们有一个由 URL 组成的数组,我们可以这样使用:
let urls = ['https://api.github.com/users/iliakan','https://api.github.com/users/remy','https://api.github.com/users/jeresig'];// 将每一个 url 映射到 promise fetch(github url)let requests = urls.map(url => fetch(url));// Promise.all 会一直等到所有的 Promise 都 resolved 了Promise.all(requests).then(responses => responses.forEachresponse => 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 => {// 所有的请求都成功返回了,我们可以显示 HTTP 的请求状态码for(let response of responses) {alert(`${response.url}: ${response.status}`); // 每个 url 请求都显示 200}return responses;})// 将每个 response.json() 的结果(即接口返回结果的数据内容)映射到数组中.then(responses => Promise.all(responses.map(r => r.json())))// 返回数据解析为对象,“users”就是由这些对象组成的数组.then(users => users.forEach(user => alert(user.name)));
如果任何一个 Promise reject 了,Promise.all 会携带错误信息立即 reject。
例如:
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 在两秒钟后 reject。这将导致 Promise.all 立即 reject,因此会执行 .catch :reject 的错误信息成为整个 Promise.all 的输出结果。
Promise 最终的一个特点是,我们不能“取消”或者“中断”它的执行。因此其他的 Promise 会继续执行,最终解决,但他们的结果将被忽略。
有一些方法可以避免这种情况:我们可以在错误的情况下编写额外的代码来 clearTimeout(或者其他取消方式)Promise,或者我们可以让错误出现在结果数组中(参见这一章稍后面的任务)。
Promise.all(iterable) 允许 iterable 中出现非 Promise 成员。
正常情况下,
Promise.all(iterable)接收一个由 Promise 对象成员组成的可迭代对象(大多数情况下是数组)。但是如果成员中有不是 Promise 的,就会被包装在Promise.resolve中。例如,本例返回的结果是
[1, 2, 3]:
Promise.all([new Promise((resolve, reject) => {setTimeout(() => resolve(1), 1000)}),2, // 看作 Promise.resolve(2)3 // 看作 Promise.resolve(3)]).then(alert); // 1, 2, 3因此,我们能够方便地将非 Promise 值传递给
Promise.all。
Promise.race
类似于 Proimse.all,这个方法也接收由 Promise 组成的可迭代对象,但是不会等到所有的 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.race 的结果输出。在第一个 Promise 解决“赢得竞赛”后,其他的的结果/错误都会被忽略。
总结
在 Promise 上存在 4 个静态方法:
Promise.resolve(value):用给定的值创建一个 resolved 状态的 Promise 对象,Promise.reject(error):用给定的错误对象创建一个 rejected 状态的 Promise 对象,Promise.all(promises):等待所有的 Promise resolve 并且返回一个数组结果,如果其中任何一个 Promise reject 了,就成为Promise.all的错误结果返回,所有其他结果都会被忽略。Promise.race(promises):等待第一个 Promise 解决,它的结果/错误成为Promise.race的输出。
在这 4 个方法里,Promise.all 在实践中最常使用。
练习题
问题
一、容错版的 Promise.all
我们平行得 fetch 多个 URL 地址。
这里是代码:
let urls = ['https://api.github.com/users/iliakan','https://api.github.com/users/remy','https://api.github.com/users/jeresig'];Promise.all(urls.map(url => fetch(url)))// 展示每一个响应的状态码.then(responses => { // (*)for(let response of responses) {alert(`${response.url}: ${response.status}`);}});
问题是,只要有一个请求失败了,Promise.all 就 reject 了,我们就丢失了其他的其他的请求结果。
这不太好。
修改代码,让 (*) 处的 responses 数组包含成功 fetch 的请求结果,错误对象中则包含失败的 fetch 请求。
例如,如果其中一个请求失败了,就应该这样:
let urls = ['https://api.github.com/users/iliakan','https://api.github.com/users/remy','http://no-such-url'];Promise.all(...) // fetch URL 地址的代码...// 将 fetch 错误的请求结果(即错误对象)也放在结果数组里....then(responses => {// 3 urls => 3 个数组成员alert(responses[0].status); // 200alert(responses[1].status); // 200alert(responses[2]); // TypeError: failed to fetch (文本可能略有不同)});
P.S. 本任务中,无需使用 response.text() 或 response.json() 加载完整的响应。只要能正确处理 fetch 失败的错误就行。
二、容错版的 Promise.all(需把返回数据处理成 JSON 对象)
将上一个任务改进一下。现在不是只要调用 fetch 就够了,也需要将从指定 URL 地址加载的数据解析成 JSON 对象。
这是代码:
let urls = ['https://api.github.com/users/iliakan','https://api.github.com/users/remy','https://api.github.com/users/jeresig'];// 产生 fetch 请求Promise.all(urls.map(url => fetch(url)))// 将每个 response 映射成 response.json().then(responses => Promise.all(responses.map(r => r.json())))// 显示每个用户的名字.then(users => { // (*)for(let user of users) {alert(user.name);}});
现在问题是,如果有请求失败了,Promise.all 就 reject 了,我们会丢失其他请求的结果。因此上面的代码不是能容错的,就像之前的任务那样。
修改代码,让 (*) 处的数组包含请求成功的和请求失败的结果。
需要注意的是,错误在 fetch()(即网络请求失败)和 response.json()(即响应不是有效的 JSON 数据) 时都有可能发生。这两种情况下的错误都会成为返回的结果数组里的成员。
答案
一、容错版的 Promise.all
解决方案还是挺简单的。
我们看下代码:
Promise.all([fetch('https://api.github.com/users/iliakan'),fetch('https://api.github.com/users/remy'),fetch('http://no-such-url')])
在 Promise.all 中,这里有一个由 fetch(...) 请求返回的 Promise 组成的数组。
我们不能修改 Promise.all 这个方法的行为:如果检测到错误,就会 reject。因此我们需要阻止错误发生。相反,当一个 fetch 发生错误的时候,我们需要把错误当做“正常”的结果。
就是下面这样做的:
Promise.all(fetch('https://api.github.com/users/iliakan').catch(err => err),fetch('https://api.github.com/users/remy').catch(err => err),fetch('http://no-such-url').catch(err => err))
也就是说,.catch 捕获了所有 Promise 中发生的错误,并且正常返回。根据 Promise 的运行规则,如果在 .then/catch 处理器中返回了一个值(不管是一个错误对象还是别的什么),就继续执行“正常”的流程。
因此,在 Promise.all 外部 .catch 返回的结果被当做“正常”结果返回了。
这段代码:
Promise.all(urls.map(url => fetch(url)))
可以被重写为:
Promise.all(urls.map(url => fetch(url).catch(err => err)))
二、容错版的 Promise.all(需把返回数据处理成 JSON 对象)
// 之前的 Promise 链会因为一个错误的发生,而整个 reject// 修改为:// 将出现的错误对象也作为结果数组的成员返回给用户let urls = ['https://api.github.com/users/iliakan',// 请求这个 URL 地址得到是一个 HTML 网页,它是无效 JSON,因此 response.json() 会失败'/',// 这个 URL 地址是无效的,因此 fetch 失败'http://no-such-url'];// 修改为:Promise.all(urls.map(url => fetch(url).catch(err => err))).then(responses => Promise.all(// 如果请求出错,直接返回错误对象// 否则对 response.json() 做捕获错误的处理,返回它的处理结果responses.map(r => r instanceof Error ? r : r.json().catch(err => err)))).then(results => {alert(results[0].name); // Ilya Kantoralert(results[1]); // SyntaxError: Unexpected token < in JSON at position 0alert(results[2]); // TypeError: failed to fetch (text may vary)});
在线查看。
(完)
Promise.all(iterable) 允许 iterable 中出现非 Promise 成员。