web-dev-qa-db-ja.com

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

MDN を見ると、Promise.allのthen()コールバックに渡されたvaluesに、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:そのようなコードを実行すると、これはもちろん証拠ではありませんが、偶然かもしれませんが、これは本当のように見えました。

146

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

リンクした仕様に従って、 Promise.all(iterable)iterable(つまり、 Iterator インターフェイスをサポートするオブジェクト)をパラメーターとして受け取り、後で呼び出し PerformPromiseAll( iterator, constructor, resultCapability) それで、後者は IteratorStep(iterator) を使用してiterableをループします。
これは、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));
});
203
Nit

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

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

26
Bergi

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

ただし、注文はクライアント側でのみ保持されることに注意してください。

開発者にとっては、約束は順番どおりに履行されたように見えますが、実際には、約束はさまざまな速度で処理されます。バックエンドは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つのPromise(A、B、C)がPromise.allに与えられています。 3つのPromiseは異なる速度で実行されます(Cが最も速く、Bが最も遅い)。そのため、Promiseの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)']
22