ホームページ > 記事 > ウェブフロントエンド > Promise の all()、race()、および allSettled() メソッドを理解する
ES6 以降では、主に Promise.all()
と Promise.race()
、Promise.allSettled( )
この提案はステージ 4 に達したため、ECMAScript 2020
の一部となる予定です。
##Promise.all(promise: Iterable
Promise
インスタンスを返します。このインスタンスは iterable
パラメーター内のすべての promise
が「解決」されるか、パラメーターに promise
が含まれていない場合、コールバックは完了 (解決) します。 ## 失敗があります (拒否)、このインスタンスのコールバックは失敗します (拒否)、失敗の理由は最初に失敗した結果です promise
Promise .race
Promise.allSettled
(promises: Iterable
2. 復習: Promise の状態
Promisefulfilled: 操作が正常に完了したことを意味します。 拒否: 操作が失敗したことを意味します。
解決済み:
3. 組み合わせとは
プリミティブ関数 (略してプリミティブ) はアトミック ブロックを作成します。
結合関数:
Promise.all()、
Promise.allSettled()
4. Promise.all()
Promise.all()
:
) ステータス
を同期的に返します。約束###。すべての受信 promise が完了するか、受信反復可能オブジェクトに
promise がない場合、
Promise.all は
promise を返します。非同期的に満たされます。
いずれの場合も、
Promise.all によって返される
promise の完了ステータスの結果は配列であり、すべての受信反復パラメーター オブジェクトの値が含まれます (約束されていない値も含みます)。
失敗/拒否:
受信した
promise の 1 つが失敗した場合 (
rejected)、
Promise.all
promise が完了したかどうかに関係なく、失敗した結果を失敗ステータスのコールバック関数に非同期的に渡します。
例を見てみましょう: <pre class="brush:js;toolbar:false;"> const promises = [
Promise.resolve(&#39;a&#39;),
Promise.resolve(&#39;b&#39;),
Promise.resolve(&#39;c&#39;),
];
Promise.all(promises)
.then((arr) => assert.deepEqual(
arr, [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;]
));</pre>
Promise の 1 つが拒否された場合に何が起こるか: <pre class="brush:js;toolbar:false;"> const promises = [
Promise.resolve(&#39;a&#39;),
Promise.resolve(&#39;b&#39;),
Promise.reject(&#39;ERROR&#39;),
];
Promise.all(promises)
.catch((err) => assert.equal(
err, &#39;ERROR&#39;
));</pre>
次の図は、Promise.all()
を示しています。仕組み
.map( )# など) ##、.filter()
など、同期計算に使用されます。たとえば、
function timesTwoSync(x) { return 2 * x; } const arr = [1, 2, 3]; const result = arr.map(timesTwoSync); assert.deepEqual(result, [2, 4, 6]);
.map()
のコールバックが.map() によって返される結果は
Promises の配列になります。
Promises
数组不是普通代码可以使用的数据,但我们可以通过Promise.all()
来解决这个问题:它将Promises数组转换为Promise
,并使用一组普通值数组来实现。
function timesTwoAsync(x) { return new Promise(resolve => resolve(x * 2)); } const arr = [1, 2, 3]; const promiseArr = arr.map(timesTwoAsync); Promise.all(promiseArr) .then(result => { assert.deepEqual(result, [2, 4, 6]); });
接下来,咱们使用.map()
和Promise.all()
从Web
下载文件。 首先,咱们需要以下帮助函数:
function downloadText(url) { return fetch(url) .then((response) => { // (A) if (!response.ok) { // (B) throw new Error(response.statusText); } return response.text(); // (C) }); }
downloadText()
使用基于Promise
的fetch API 以字符串流的方式下载文件:
.text()
(第C行)以字符串的形式取回文件的内容。在下面的示例中,咱们 下载了两个文件
const urls = [ 'http://example.com/first.txt', 'http://example.com/second.txt', ]; const promises = urls.map( url => downloadText(url)); Promise.all(promises) .then( (arr) => assert.deepEqual( arr, ['First!', 'Second!'] ));
function all(iterable) { return new Promise((resolve, reject) => { let index = 0; for (const promise of iterable) { // Capture the current value of `index` const currentIndex = index; promise.then( (value) => { if (anErrorOccurred) return; result[currentIndex] = value; elementCount++; if (elementCount === result.length) { resolve(result); } }, (err) => { if (anErrorOccurred) return; anErrorOccurred = true; reject(err); }); index++; } if (index === 0) { resolve([]); return; } let elementCount = 0; let anErrorOccurred = false; const result = new Array(index); }); }
Promise.race()
方法的定义:
Promise.race
Promise.race(iterable) 方法返回一个 promise
,一旦迭代器中的某个promise
解决或拒绝,返回的 promise
就会解决或拒绝。来几个例子,瞧瞧:
const promises = [ new Promise((resolve, reject) => setTimeout(() => resolve('result'), 100)), // (A) new Promise((resolve, reject) => setTimeout(() => reject('ERROR'), 200)), // (B) ]; Promise.race(promises) .then((result) => assert.equal( // (C) result, 'result'));
在第 A
行,Promise
是完成状态 ,所以 第 C
行会执行(尽管第 B
行被拒绝)。
如果 Promise 被拒绝首先执行,在来看看情况是嘛样的:
const promises = [ new Promise((resolve, reject) => setTimeout(() => resolve('result'), 200)), new Promise((resolve, reject) => setTimeout(() => reject('ERROR'), 100)), ]; Promise.race(promises) .then( (result) => assert.fail(), (err) => assert.equal( err, 'ERROR'));
注意,由于 Promse
先被拒绝,所以 Promise.race()
返回的是一个被拒绝的 Promise
这意味着Promise.race([])
的结果永远不会完成。
下图演示了Promise.race()
的工作原理:
在本节中,我们将使用Promise.race()
来处理超时的 Promise
。 以下辅助函数:
function resolveAfter(ms, value=undefined) { return new Promise((resolve, reject) => { setTimeout(() => resolve(value), ms); }); }
resolveAfter()
主要做的是在指定的时间内,返回一个状态为 resolve
的 Promise
,值为为传入的 value
调用上面方法:
function timeout(timeoutInMs, promise) { return Promise.race([ promise, resolveAfter(timeoutInMs, Promise.reject(new Error('Operation timed out'))), ]); }
timeout()
返回一个Promise
,该 Promise
的状态取决于传入 promise
状态 。
其中 timeout
函数中的 resolveAfter(timeoutInMs, Promise.reject(new Error('Operation timed out'))
,通过 resolveAfter
定义可知,该结果返回的是一个被拒绝状态的 Promise
。
再来看看timeout(timeoutInMs, promise)
的运行情况。如果传入promise
在指定的时间之前状态为完成时,timeout
返回结果就是一个完成状态的 Promise
,可以通过.then
的第一个回调参数处理返回的结果。
timeout(200, resolveAfter(100, 'Result!')) .then(result => assert.equal(result, 'Result!'));
相反,如果是在指定的时间之后完成,刚 timeout
返回结果就是一个拒绝状态的 Promise
,从而触发catch
方法指定的回调函数。
timeout(100, resolveAfter(2000, 'Result!')) .catch(err => assert.deepEqual(err, new Error('Operation timed out')));
重要的是要了解“Promise 超时”的真正含义:
Promise
较到的得到解决,其结果就会给返回的 Promise
。Promise
的状态为拒绝。也就是说,超时只会阻止传入的Promise,影响输出 Promise(因为Promise只能解决一次), 但它并没有阻止传入Promise
的异步操作。
以下是 Promise.race()
的一个简化实现(它不执行安全检查)
function race(iterable) { return new Promise((resolve, reject) => { for (const promise of iterable) { promise.then( (value) => { if (settlementOccurred) return; settlementOccurred = true; resolve(value); }, (err) => { if (settlementOccurred) return; settlementOccurred = true; reject(err); }); } let settlementOccurred = false; }); }
“Promise.allSettled”
这一特性是由Jason Williams,Robert Pamely和Mathias Bynens提出。
promise.allsettle()
方法的定义:
它返回一个Array
的Promise
,其元素具有以下类型特征:
type SettlementObject<T> = FulfillmentObject<T> | RejectionObject; interface FulfillmentObject<T> { status: 'fulfilled'; value: T; } interface RejectionObject { status: 'rejected'; reason: unknown; }
Promise.allSettled()
方法返回一个promise,该promise在所有给定的promise已被解析或被拒绝后解析,并且每个对象都描述每个promise的结果。
举例说明, 比如各位用户在页面上面同时填了3个独立的表单, 这三个表单分三个接口提交到后端, 三个接口独立, 没有顺序依赖, 这个时候我们需要等到请求全部完成后给与用户提示表单提交的情况
在多个promise
同时进行时咱们很快会想到使用Promise.all
来进行包装, 但是由于Promise.all
的短路特性, 三个提交中若前面任意一个提交失败, 则后面的表单也不会进行提交了, 这就与咱们需求不符合.
Promise.allSettled
跟Promise.all
类似, 其参数接受一个Promise
的数组, 返回一个新的Promise
, 唯一的不同在于, 其不会进行短路, 也就是说当Promise
全部处理完成后我们可以拿到每个Promise
的状态, 而不管其是否处理成功.
下图说明promise.allsettle()
是如何工作的
这是Promise.allSettled()
使用方式快速演示示例
Promise.allSettled([ Promise.resolve('a'), Promise.reject('b'), ]) .then(arr => assert.deepEqual(arr, [ { status: 'fulfilled', value: 'a' }, { status: 'rejected', reason: 'b' }, ]));
这个示例类似于.map()
和Promise.all()
示例(我们从其中借用了downloadText()
函数):我们下载多个文本文件,这些文件的url
存储在一个数组中。但是,这一次,咱们不希望在出现错误时停止,而是希望继续执行。Promise.allSettled()
允许咱们这样做:
const urls = [ 'http://example.com/exists.txt', 'http://example.com/missing.txt', ]; const result = Promise.allSettled( urls.map(u => downloadText(u))); result.then( arr => assert.deepEqual( arr, [ { status: 'fulfilled', value: 'Hello!', }, { status: 'rejected', reason: new Error('Not Found'), }, ] ));
这是promise.allsettle()
的简化实现(不执行安全检查)
function allSettled(iterable) { return new Promise((resolve, reject) => { function addElementToResult(i, elem) { result[i] = elem; elementCount++; if (elementCount === result.length) { resolve(result); } } let index = 0; for (const promise of iterable) { // Capture the current value of `index` const currentIndex = index; promise.then( (value) => addElementToResult( currentIndex, { status: 'fulfilled', value }), (reason) => addElementToResult( currentIndex, { status: 'rejected', reason })); index++; } if (index === 0) { resolve([]); return; } let elementCount = 0; const result = new Array(index); }); }
Promise.all()
和 romise.race()
都具有 短路特性
promise
有一个失败(rejected),此实例回调失败(reject)Promise.race():如果参数中某个promise
解决或拒绝,返回的 promise就会解决或拒绝。
考虑下面的代码:
asyncFunc1() .then(result1 => { assert.equal(result1, 'one'); return asyncFunc2(); }) .then(result2 => { assert.equal(result2, 'two'); });
使用.then()
顺序执行基于Promise
的函数:只有在 asyncFunc1()
的结果被解决后才会执行asyncFunc2()
。
而 Promise.all()
是并发执行的
Promise.all([asyncFunc1(), asyncFunc2()]) .then(arr => { assert.deepEqual(arr, ['one', 'two']); });
确定并发异步代码的技巧:关注异步操作何时启动,而不是如何处理它们的Promises。
例如,下面的每个函数都同时执行asyncFunc1()
和asyncFunc2()
,因为它们几乎同时启动。
function concurrentAll() { return Promise.all([asyncFunc1(), asyncFunc2()]); } function concurrentThen() { const p1 = asyncFunc1(); const p2 = asyncFunc2(); return p1.then(r1 => p2.then(r2 => [r1, r2])); }
另一方面,以下两个函数依次执行asyncFunc1()
和asyncFunc2()
: asyncFunc2()
仅在asyncFunc1()
的解决之后才调用。
function sequentialThen() { return asyncFunc1() .then(r1 => asyncFunc2() .then(r2 => [r1, r2])); } function sequentialAll() { const p1 = asyncFunc1(); const p2 = p1.then(() => asyncFunc2()); return Promise.all([p1, p2]); }
Promise.all()
与并发模式“fork join”松散相关。重温一下咱们前面的一个例子:
Promise.all([ // (A) fork downloadText('http://example.com/first.txt'), downloadText('http://example.com/second.txt'), ]) // (B) join .then( (arr) => assert.deepEqual( arr, ['First!', 'Second!'] ));
A
行中,分割两个异步任务并同时执行它们。B
行中,对每个小任务得到的结果进行汇总。英文原文:https://2ality.com/2019/08/promise-combinators.html
更多编程相关知识,请访问:编程教学!!
以上がPromise の all()、race()、および allSettled() メソッドを理解するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。