From 99b39cc2300015a8e194ac9466a9cf460878e526 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 6 Oct 2019 20:11:13 +0800 Subject: [PATCH] docs(Promise): edit Promise --- docs/promise.md | 141 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 135 insertions(+), 6 deletions(-) diff --git a/docs/promise.md b/docs/promise.md index 27ea878..4b62999 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -567,13 +567,13 @@ Promise.reject(3).finally(() => {}) ## Promise.all() -`Promise.all`方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 +`Promise.all()`方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 ```javascript const p = Promise.all([p1, p2, p3]); ``` -上面代码中,`Promise.all`方法接受一个数组作为参数,`p1`、`p2`、`p3`都是 Promise 实例,如果不是,就会先调用下面讲到的`Promise.resolve`方法,将参数转为 Promise 实例,再进一步处理。(`Promise.all`方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。) +上面代码中,`Promise.all()`方法接受一个数组作为参数,`p1`、`p2`、`p3`都是 Promise 实例,如果不是,就会先调用下面讲到的`Promise.resolve`方法,将参数转为 Promise 实例,再进一步处理。另外,`Promise.all()`方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。 `p`的状态由`p1`、`p2`、`p3`决定,分成两种情况。 @@ -662,7 +662,7 @@ Promise.all([p1, p2]) ## Promise.race() -`Promise.race`方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。 +`Promise.race()`方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。 ```javascript const p = Promise.race([p1, p2, p3]); @@ -670,7 +670,7 @@ const p = Promise.race([p1, p2, p3]); 上面代码中,只要`p1`、`p2`、`p3`之中有一个实例率先改变状态,`p`的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给`p`的回调函数。 -`Promise.race`方法的参数与`Promise.all`方法一样,如果不是 Promise 实例,就会先调用下面讲到的`Promise.resolve`方法,将参数转为 Promise 实例,再进一步处理。 +`Promise.race()`方法的参数与`Promise.all()`方法一样,如果不是 Promise 实例,就会先调用下面讲到的`Promise.resolve()`方法,将参数转为 Promise 实例,再进一步处理。 下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为`reject`,否则变为`resolve`。 @@ -689,9 +689,138 @@ p 上面代码中,如果 5 秒之内`fetch`方法无法返回结果,变量`p`的状态就会变为`rejected`,从而触发`catch`方法指定的回调函数。 +## Promise.allSettled() + +`Promise.allSettled()`方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是`fulfilled`还是`rejected`,包装实例才会结束。该方法由[ES2020](https://github.com/tc39/proposal-promise-allSettled) 引入。 + +```javascript +const promises = [ + fetch('/api-1'), + fetch('/api-2'), + fetch('/api-3'), +]; + +await Promise.allSettled(promises); +removeLoadingIndicator(); +``` + +上面代码对服务器发出三个请求,等到三个请求都结束,不管请求成功还是失败,加载的滚动图标就会消失。 + +该方法返回的新的 Promise 实例,一旦结束,状态总是`fulfilled`,不会变成`rejected`。状态变成`fulfilled`后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入`Promise.allSettled()`的 Promise 实例。 + +```javascript +const resolved = Promise.resolve(42); +const rejected = Promise.reject(-1); + +const allSettledPromise = Promise.allSettled([resolved, rejected]); + +allSettledPromise.then(function (results) { + console.log(results); +}); +// [ +// { status: 'fulfilled', value: 42 }, +// { status: 'rejected', reason: -1 } +// ] +``` + +上面代码中,`Promise.allSettled()`的返回值`allSettledPromise`,状态只可能变成`fulfilled`。它的监听函数接收到的参数是数组`results`。该数组的每个成员都是一个对象,对应传入`Promise.allSettled()`的两个 Promise 实例。每个对象都有`status`属性,该属性的值只可能是字符串`fulfilled`或字符串`rejected`。`fulfilled`时,对象有`value`属性,`rejected`时有`reason`属性,对应两种状态的返回值。 + +下面是返回值用法的例子。 + +```javascript +const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ]; +const results = await Promise.allSettled(promises); + +// 过滤出成功的请求 +const successfulPromises = results.filter(p => p.status === 'fulfilled'); + +// 过滤出失败的请求,并输出原因 +const errors = results + .filter(p => p.status === 'rejected') + .map(p => p.reason); +``` + +有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,`Promise.allSettled()`方法就很有用。如果没有这个方法,想要确保所有操作都结束,就很麻烦。`Promise.all()`方法无法做到这一点。 + +```javascript +const urls = [ /* ... */ ]; +const requests = urls.map(x => fetch(x)); + +try { + await Promise.all(requests); + console.log('所有请求都成功。'); +} catch { + console.log('至少一个请求失败,其他请求可能还没结束。'); +} +``` + +上面代码中,`Promise.all()`无法确定所有请求都结束。想要达到这个目的,写起来很麻烦,有了`Promise.allSettled()`,这就很容易了。 + +## Promise.any() + +`Promise.any()`方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成`fulfilled`状态,包装实例就会变成`fulfilled`状态;如果所有参数实例都变成`rejected`状态,包装实例就会变成`rejected`状态。该方法目前是一个第三阶段的[提案](https://github.com/tc39/proposal-promise-any) 。 + +`Promise.all()`跟`Promise.race()`方法很像,只有一点不同,就是不会因为某个 Promise 变成`rejected`状态而结束。 + +```javascript +const promises = [ + fetch('/endpoint-a').then(() => 'a'), + fetch('/endpoint-b').then(() => 'b'), + fetch('/endpoint-c').then(() => 'c'), +]; +try { + const first = await Promise.any(promises); + console.log(first); +} catch (error) { + console.log(error); +} +``` + +上面代码中,`Promise.any()`方法的参数数组包含三个 Promise 操作。其中只要有一个变成`fulfilled`,`Promise.any()`返回的 Promise 对象就变成`fulfilled`。如果所有三个操作都变成`rejected`,那么就会`await`命令就会抛出错误。 + +`Promise.any()`抛出的错误,不是一个一般的错误,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被`rejected`的操作所抛出的错误。下面是 AggregateError 的实现示例。 + +```javascript +new AggregateError() extends Array -> AggregateError + +const err = new AggregateError(); +err.push(new Error("first error")); +err.push(new Error("second error")); +throw err; +``` + +捕捉错误时,如果不用`try...catch`结构和 await 命令,可以像下面这样写。 + +```javascript +Promise.any(promises).then( + (first) => { + // Any of the promises was fulfilled. + }, + (error) => { + // All of the promises were rejected. + } +); +``` + +下面是一个例子。 + +```javascript +var resolved = Promise.resolve(42); +var rejected = Promise.reject(-1); +var alsoRejected = Promise.reject(Infinity); + +Promise.any([resolved, rejected, alsoRejected]).then(function (result) { + console.log(result); // 42 +}); + +Promise.any([rejected, alsoRejected]).catch(function (results) { + console.log(results); // [-1, Infinity] +}); +``` + ## Promise.resolve() -有时需要将现有对象转为 Promise 对象,`Promise.resolve`方法就起到这个作用。 +有时需要将现有对象转为 Promise 对象,`Promise.resolve()`方法就起到这个作用。 ```javascript const jsPromise = Promise.resolve($.ajax('/whatever.json')); @@ -699,7 +828,7 @@ const jsPromise = Promise.resolve($.ajax('/whatever.json')); 上面代码将 jQuery 生成的`deferred`对象,转为一个新的 Promise 对象。 -`Promise.resolve`等价于下面的写法。 +`Promise.resolve()`等价于下面的写法。 ```javascript Promise.resolve('foo')