Promise.all:解決された値の順序


189

MDNを見ると、Promise.all valuesthen()コールバックに渡されたように見えます。promiseの順序で値が含まれています。例えば:

var somePromises = [1, 2, 3, 4, 5].map(Promise.resolve);
return Promise.all(somePromises).then(function(results) {
  console.log(results) //  is [1, 2, 3, 4, 5] the guaranteed result?
});

誰がどの順序であるvaluesべきかを明記した仕様を引用できますか?

PS:そのようなコードを実行すると、これは事実であるように見えますが、もちろんそれは証明ではありません。偶然かもしれません。

回答:


274

まもなく、順序は保持されます。

リンクした仕様に従って、はパラメーターとして(つまり、インターフェースをサポートするオブジェクト)をPromise.all(iterable)受け取り、後でそれを使用して呼び出します。後者はを使用してループします。 つまり、渡したイテラブルが厳密に順序付けられている場合でも、渡された後も順序付けされます。iterableIteratorPerformPromiseAll( iterator, constructor, resultCapability)iterableIteratorStep(iterator)
Promise.all()

解決は、解決されPromise.all() Resolveた各プロミスに内部[[Index]]スロットがあり、元の入力のプロミスのインデックスをマークすることによって実装されます。


これはすべて、入力が厳密に順序付けされている限り(たとえば、配列)、出力が入力として厳密に順序付けられることを意味します。

以下のフィドル(ES6)で、この動作を確認できます。

// Used to display results
const write = msg => {
  document.body.appendChild(document.createElement('div')).innerHTML = msg;
};

// Different speed async operations
const slow = new Promise(resolve => {
  setTimeout(resolve, 200, 'slow');
});
const instant = 'instant';
const quick = new Promise(resolve => {
  setTimeout(resolve, 50, 'quick');
});

// The order is preserved regardless of what resolved first
Promise.all([slow, instant, quick]).then(responses => {
  responses.map(response => write(response));
});


1
反復可能オブジェクトを厳密に注文しない方法は?任意の反復可能は、それが中にその値を生成するためで、「厳密に命じた」されています。
ベンジャミンGruenbaum

注-Firefoxは、Promiseでイテラブルを正しく実装する唯一のブラウザです。throwイテラブルをに渡すと、Chromeは現在例外となりますPromise.all。さらに、多くの人がそれを議論し、その時点で反対することを決定しましたが、私は現在、反復可能オブジェクトを渡すことをサポートするユーザーランドプロミスの実装を知りません。
Benjamin Gruenbaum、2015年

3
@BenjaminGruenbaum 2回繰り返されると2つの異なる注文を生成するイテラブルを持つことは可能ではないでしょうか?たとえば、反復されるとランダムな順序でカードを生成する一連のカードですか?ここで「厳密に順序付けられた」が正しい用語であるかどうかはわかりませんが、すべての反復可能オブジェクトの順序が決まっているわけではありません。したがって、イテレータは「厳密に順序付けられている」(それが正しい用語であると仮定すると)と言うのが妥当だと思いますが、イテラブルはそうではありません。
JLRishe

3
@JLRishe私はあなたが正しいと思います、順序付けされているのは確かにイテレータです-イテラブルはそうではありません。
Benjamin Gruenbaum 2015年

8
約束が連鎖しないことは注目に値します。同じ順序で解決策が得られますが、約束がいつ実行されるかについての保証はありません。つまり、Promise.allpromiseの配列を順番に実行するために使用することはできません。イテレータにロードされたプロミスは、これが予測どおりに機能するために互いに独立している必要があります。
Andrew Eddie

49

以前の回答ですでに述べたように、はPromise.allすべての解決された値を元のPromiseの入力順序に対応する配列で集約します(Promiseの集約を参照)。

ただし、注文はクライアント側でのみ保存されることを指摘しておきます。

開発者にとって、約束は順調に満たされているように見えますが、実際には、約束はさまざまな速度で処理されます。バックエンドは別の順序でPromiseを受信する可能性があるため、リモートバックエンドを使用するときにこれを知ることが重要です。

タイムアウトを使用して問題を示す例を次に示します。

Promise.all

const myPromises = [
  new Promise((resolve) => setTimeout(() => {resolve('A (slow)'); console.log('A (slow)')}, 1000)),
  new Promise((resolve) => setTimeout(() => {resolve('B (slower)'); console.log('B (slower)')}, 2000)),
  new Promise((resolve) => setTimeout(() => {resolve('C (fast)'); console.log('C (fast)')}, 10))
];

Promise.all(myPromises).then(console.log)

上記のコードでは、3つの約束(A、B、C)がに与えられていPromise.allます。3つのプロミスは異なる速度で実行されます(Cが最も速く、Bが最も遅い)。そのため、console.log約束のステートメントは次の順序で表示されます。

C (fast) 
A (slow)
B (slower)

PromiseがAJAX呼び出しの場合、リモートバックエンドはこれらの値をこの順序で受け取ります。ただし、クライアント側Promise.allでは、myPromises配列の元の位置に従って結果が順序付けられるようにします。そのため、最終結果は次のようになります。

['A (slow)', 'B (slower)', 'C (fast)']

Promiseの実際の実行も保証する場合は、Promiseキューのような概念が必要になります。以下は、p-queueの使用例です(注意:すべてのPromiseを関数でラップする必要があります)。

シーケンシャルプロミスキュー

const PQueue = require('p-queue');
const queue = new PQueue({concurrency: 1});

// Thunked Promises:
const myPromises = [
  () => new Promise((resolve) => setTimeout(() => {
    resolve('A (slow)');
    console.log('A (slow)');
  }, 1000)),
  () => new Promise((resolve) => setTimeout(() => {
    resolve('B (slower)');
    console.log('B (slower)');
  }, 2000)),
  () => new Promise((resolve) => setTimeout(() => {
    resolve('C (fast)');
    console.log('C (fast)');
  }, 10))
];

queue.addAll(myPromises).then(console.log);

結果

A (slow)
B (slower)
C (fast)

['A (slow)', 'B (slower)', 'C (fast)']

2
すばらしい回答、特にPQueueを使用
ironstein

シーケンシャルプロミスキューが必要ですが、結果のSQLレコードから実行する必要がある場合はどうすればよいですか?のために?一方、ES2017にはES2018の代替案はありませんか?
stackdave 2018

PQueueは私を助けてくれました!ありがとうございました!:)
podeig

28

はい、の値resultsはと同じ順序promisesです。

ES6の仕様をPromise.allで引用するかもしれませんが、使用されている反復子APIと汎用のpromiseコンストラクターのために少し複雑です。ただし、各リゾルバーコールバックには[[index]]、promise-arrayの反復で作成され、結果の配列に値を設定するために使用される属性があることがわかります。


奇妙なことに、私は今日、出力順序は最初に解決した人によって決定され、次に2番目に決定されるというyoutubeビデオを見ました。ビデオOPが間違っていたと思いますか?
Royi Namir 2015

1
@RoyiNamir:どうやら彼はそうだった。
Bergi、2015年

@オジルワット?すべての約束がいつ満たされるかは、解決の年代順は絶対に重要ではありません。結果配列の値の順序は、promiseの入力配列と同じです。そうでない場合は、適切なpromise実装に切り替える必要があります。
ベルギ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.