Promise.all: orden de valores resueltos

190

Al mirar MDN , parece que se valuespasó a la then()devolución de llamada de Promise.all contiene los valores en el orden de las promesas. Por ejemplo:

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

¿Alguien puede citar una especificación que indique en qué orden valuesdebe estar?

PD: Ejecutar un código como ese demostró que esto parece ser cierto, aunque eso no es una prueba, podría haber sido una coincidencia.

Thorben Croisé
fuente

Respuestas:

274

En breve, se conserva el orden .

Siguiendo la especificación a la que se vinculó, Promise.all(iterable)toma un parámetro iterable(es decir, un objeto que admite la Iteratorinterfaz) y luego lo llama PerformPromiseAll( iterator, constructor, resultCapability), donde este último deja de iterableusarlo IteratorStep(iterator).
Esto significa que si el iterable al que pasas Promise.all()está estrictamente ordenado, aún se ordenarán una vez que lo pases .

La resolución se implementa a través de Promise.all() Resolvedonde cada promesa resuelta tiene una [[Index]]ranura interna , que marca el índice de la promesa en la entrada original.


Todo esto significa que la salida está estrictamente ordenada como entrada, siempre que la entrada esté estrictamente ordenada (por ejemplo, una matriz).

Puede ver esto en acción en el siguiente violín (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));
});

Liendre
fuente
1
¿Cómo no se puede ordenar estrictamente un iterable? Cualquier iterable está "estrictamente ordenado" por el orden en el que produce sus valores.
Benjamin Gruenbaum
Nota: Firefox es el único navegador que implementa iterables en las promesas correctamente. Chrome será actualmente throwuna excepción si le pasas un iterable a Promise.all. Además, no estoy al tanto de ninguna implementación de promesa de tierra de usuario que actualmente sea compatible con iteraciones pasables, aunque muchos lo han debatido y decidido no hacerlo en ese momento.
Benjamin Gruenbaum
3
@BenjaminGruenbaum ¿No es posible tener un iterable que produzca dos órdenes diferentes al repetirse dos veces? Por ejemplo, ¿una baraja de cartas que produce cartas en orden aleatorio cuando se itera? No sé si "estrictamente ordenado" es la terminología correcta aquí, pero no todos los iterables tienen un orden fijo. Así que creo que es razonable decir que los iteradores están "estrictamente ordenados" (suponiendo que sea el término correcto), pero los iterables no lo son.
JLRishe
3
@JLRishe Supongo que tienes razón, de hecho son los iteradores los que están ordenados, los iterables no.
Benjamin Gruenbaum
8
Vale la pena señalar que las promesas no encadenan. Si bien obtendrá la resolución en el mismo orden, no hay garantía sobre cuándo se cumplirán las promesas. En otras palabras, Promise.allno se puede utilizar para ejecutar una serie de promesas en orden, una tras otra. Las promesas cargadas en el iterador deben ser independientes entre sí para que esto funcione de manera predecible.
Andrew Eddie
49

Como ya se ha indicado en las respuestas anteriores, Promise.allagrega todos los valores resueltos con una matriz correspondiente al orden de entrada de las Promesas originales (consulte Agregación de promesas ).

Sin embargo, me gustaría señalar que el pedido solo se conserva en el lado del cliente.

Para el desarrollador, parece que las promesas se cumplieron en orden, pero en realidad, las promesas se procesan a diferentes velocidades. Es importante saber cuándo trabaja con un servidor remoto porque el servidor podría recibir sus promesas en un orden diferente.

Aquí hay un ejemplo que demuestra el problema mediante el uso de tiempos de espera:

Promesa.todos

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)

En el código que se muestra arriba, se dan tres promesas (A, B, C) Promise.all. Las tres promesas se ejecutan a diferentes velocidades (C es la más rápida y B la más lenta). Es por eso que las console.logdeclaraciones de las Promesas aparecen en este orden:

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

Si las Promesas son llamadas AJAX, un backend remoto recibirá estos valores en este orden. Pero del lado del cliente Promise.allasegura que los resultados se ordenan de acuerdo con las posiciones originales de la myPromisesmatriz. Por eso el resultado final es:

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

Si desea garantizar también la ejecución real de sus Promesas, necesitaría un concepto como una cola Promesa. Aquí hay un ejemplo usando p-queue (tenga cuidado, necesita ajustar todas las promesas en las funciones):

Cola de promesa secuencial

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

Resultado

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

['A (slow)', 'B (slower)', 'C (fast)']
Benny Neugebauer
fuente
2
gran respuesta, especialmente usando PQueue
ironstein
Necesito una cola de promesa secuencial, pero ¿cómo lo hago si debo hacerlo desde un registro sql de resultados? en un para? mientras que?, no hay alternativa en ES2017 nuestro ES2018?
stackdave
¡PQueue me ayudó! ¡Gracias! :)
Podeig
28

Sí, los valores en resultsestán en el mismo orden que promises.

Uno podría citar la especificación ES6Promise.all , aunque es un poco complicado debido a la API de iterador utilizada y al constructor de promesa genérico. Sin embargo, notará que cada devolución de llamada de resolución tiene un [[index]]atributo que se crea en la iteración de matriz de promesa y se utiliza para establecer los valores en la matriz de resultados.

Bergi
fuente
Extraño, vi un video de YouTube hoy que decía que el orden de salida está determinado por el primero que resolvió, luego el segundo, luego ... ¿Supongo que el OP del video fue incorrecto?
Royi Namir
1
@RoyiNamir: Aparentemente lo estaba.
Bergi
@Ozil Wat? El orden cronológico de resolución no importa en absoluto cuando se cumplan todas las promesas. El orden de los valores en la matriz de resultados es el mismo que en la matriz de entrada de promesas. Si no es así, debe cambiar a una implementación de promesa adecuada.
Bergi