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:そのようなコードを実行すると、これはもちろん証拠ではありませんが、偶然かもしれませんが、これは本当のように見えました。
まもなく、順序は保持されます。
リンクした仕様に従って、 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));
});
はい、results
の値はpromises
と同じ順序です。
Promise.all
のES6仕様 を引用するかもしれませんが、使用されているイテレーターAPIと汎用プロミスコンストラクターのために少し複雑になっています。ただし、各リゾルバーコールバックには、[[index]]
属性があり、promise-arrayの反復で作成され、結果の配列に値を設定するために使用されます。
前の回答で既に述べたように、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)']