Promise.all: Ordre des valeurs résolues

190

En regardant MDN, il semble que le valuespassé au then()rappel de Promise.all contient les valeurs dans l'ordre des promesses. Par exemple:

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?
});

Quelqu'un peut-il citer une spécification indiquant dans quel ordre valuesdoit être placé?

PS: L'exécution d'un code comme celui-ci a montré que cela semble être vrai bien que ce ne soit bien sûr pas une preuve - cela aurait pu être une coïncidence.

Thorben Croisé
la source

Réponses:

274

Sous peu, l'ordre est conservé .

À la suite de la spécification que vous avez liée, Promise.all(iterable)prend un iterable(c'est-à-dire un objet qui prend en charge l' Iteratorinterface) comme paramètre et plus tard appelle PerformPromiseAll( iterator, constructor, resultCapability)avec lui, où ce dernier boucle en iterableutilisant IteratorStep(iterator).
Cela signifie que si l'itérable auquel vous passez Promise.all()est strictement commandé, il sera toujours commandé une fois passé.

La résolution est implémentée via Promise.all() Resolveoù chaque promesse résolue a un [[Index]]emplacement interne , qui marque l'index de la promesse dans l'entrée d'origine.


Tout cela signifie que la sortie est strictement ordonnée comme entrée tant que l'entrée est strictement ordonnée (par exemple, un tableau).

Vous pouvez le voir en action dans le violon ci-dessous (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));
});

Lente
la source
1
Comment un itérable ne serait-il pas strictement ordonné? Tout itérable est "strictement ordonné" par l'ordre dans lequel il produit ses valeurs.
Benjamin Gruenbaum
Remarque - Firefox est le seul navigateur à implémenter correctement les itérables dans les promesses. Chrome sera actuellement throwune excpetion si vous passez un itérable à Promise.all. De plus, je ne connais aucune implémentation de promesse userland qui prend actuellement en charge le passage d'itérables bien que beaucoup en aient débattu et se soient prononcées contre elle à l'époque.
Benjamin Gruenbaum
3
@BenjaminGruenbaum N'est-il pas possible d'avoir un itérable qui produit deux ordres différents après avoir été itéré deux fois? Par exemple, un jeu de cartes qui produit des cartes dans un ordre aléatoire lorsqu'il est itéré? Je ne sais pas si «strictement ordonné» est la bonne terminologie ici, mais tous les itérables n'ont pas un ordre fixe. Je pense donc qu'il est raisonnable de dire que les itérateurs sont «strictement ordonnés» (en supposant que ce soit le bon terme), mais que les itérables ne le sont pas.
JLRishe
3
@JLRishe Je suppose que vous avez raison, ce sont en effet les itérateurs qui sont ordonnés - les itérables ne le sont pas.
Benjamin Gruenbaum
8
Il convient de noter que les promesses ne s'enchaînent pas. Bien que vous obteniez la résolution dans le même ordre, il n'y a aucune garantie quant au moment où les promesses seront exécutées. En d'autres termes, Promise.allne peut pas être utilisé pour exécuter un tableau de promesses dans l'ordre, les unes après les autres. Les promesses chargées dans l'itérateur doivent être indépendantes les unes des autres pour que cela fonctionne de manière prévisible.
Andrew Eddie
49

Comme les réponses précédentes l'ont déjà indiqué, Promise.allagrège toutes les valeurs résolues avec un tableau correspondant à l'ordre d'entrée des promesses d'origine (voir Agrégation des promesses ).

Cependant, je tiens à préciser que la commande n'est conservée que côté client!

Pour le développeur, il semble que les promesses ont été remplies dans l'ordre, mais en réalité, les promesses sont traitées à des vitesses différentes. Ceci est important à savoir lorsque vous travaillez avec un backend distant car le backend peut recevoir vos promesses dans un ordre différent.

Voici un exemple qui illustre le problème en utilisant des délais d'expiration:

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)

Dans le code ci-dessus, trois promesses (A, B, C) sont données Promise.all. Les trois promesses s'exécutent à des vitesses différentes (C étant le plus rapide et B étant le plus lent). C'est pourquoi les console.logdéclarations des promesses apparaissent dans cet ordre:

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

Si les promesses sont des appels AJAX, un backend distant recevra ces valeurs dans cet ordre. Mais du côté client Promise.allgarantit que les résultats sont classés selon les positions d'origine du myPromisestableau. C'est pourquoi le résultat final est:

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

Si vous souhaitez garantir également l'exécution réelle de vos promesses, vous aurez besoin d'un concept tel qu'une file d'attente de promesses. Voici un exemple utilisant p-queue (attention, vous devez envelopper toutes les promesses dans des fonctions):

File d'attente de promesses séquentielle

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);

Résultat

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

['A (slow)', 'B (slower)', 'C (fast)']
Benny Neugebauer
la source
2
excellente réponse, en particulier en utilisant PQueue
ironstein
J'ai besoin d'une file d'attente de promesses séquentielles, mais comment faire si je dois le faire à partir d'un résultat des enregistrements SQL? dans un pour? alors?, pas d'alternative dans ES2017 notre ES2018?
stackdave
PQueue m'a aidé! Je vous remercie! :)
podeig le
28

Oui, les valeurs de resultssont dans le même ordre que le promises.

On pourrait citer la spécification ES6Promise.all , même si elle est un peu compliquée en raison de l'api de l'itérateur utilisé et du constructeur de promesse générique. Cependant, vous remarquerez que chaque rappel de résolveur a un [[index]]attribut qui est créé dans l'itération du tableau de promesse et utilisé pour définir les valeurs sur le tableau de résultats.

Bergi
la source
Bizarre, j'ai vu une vidéo sur youtube aujourd'hui qui disait que l'ordre de sortie est déterminé par le premier qui a résolu, puis le second, puis ..... Je suppose que la vidéo OP était erronée?
Royi Namir
1
@RoyiNamir: Apparemment, il l'était.
Bergi
@Ozil Wat? L'ordre chronologique de résolution n'a absolument pas d'importance lorsque toutes les promesses sont tenues. L'ordre des valeurs dans le tableau de résultats est le même que dans le tableau d'entrée des promesses. Si ce n'est pas le cas, vous devez passer à une implémentation de promesse appropriée.
Bergi