Promise.all: ordem dos valores resolvidos

189

Observando o MDN , parece que o valuespassado para o then()retorno de chamada de Promise.all contém os valores na ordem das promessas. Por exemplo:

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

Alguém pode citar uma especificação informando em que ordem valuesdeve estar?

PS: Executar um código como esse mostrou que isso parece verdade, embora isso não seja prova - poderia ter sido coincidência.

Thorben Croisé
fonte

Respostas:

274

Logo, o pedido é preservado .

Após a especificação à qual você se vinculou, Promise.all(iterable)leva um iterable(ou seja, um objeto que suporta a Iteratorinterface) como parâmetro e, posteriormente, faz uma chamada PerformPromiseAll( iterator, constructor, resultCapability)com ele, onde o último faz iterableuso repetido IteratorStep(iterator).
Isso significa que, se o iterável para o qual você entra Promise.all()for estritamente ordenado, ele ainda será solicitado assim que for passado.

A resolução é implementada através do Promise.all() Resolvelocal em que cada promessa resolvida possui um [[Index]]slot interno , que marca o índice da promessa na entrada original.


Tudo isso significa que a saída é estritamente ordenada como entrada, desde que estritamente ordenada (por exemplo, uma matriz).

Você pode ver isso em ação no violino abaixo (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));
});

Nit
fonte
1
Como um iterável não seria estritamente ordenado? Qualquer iterável é "estritamente ordenado" pela ordem em que produz seus valores.
Benjamin Gruenbaum
Nota - O Firefox é o único navegador que implementa iterables nas promessas corretamente. No momento, o Chrome será throwuma exceção se você passar um iterável para Promise.all. Além disso, não estou ciente de nenhuma implementação de promessa de usuário que atualmente ofereça suporte a iterables, embora muitos tenham debatido e decidido contra isso na época.
Benjamin Gruenbaum
3
@BenjaminGruenbaum Não é possível ter um iterável que produz duas ordens diferentes ao ser iterado duas vezes? Por exemplo, um baralho de cartas que produz cartas em ordem aleatória quando é iterado? Não sei se "estritamente ordenada" é a terminologia correta aqui, mas nem todas as iteráveis ​​têm uma ordem fixa. Portanto, acho razoável dizer que os iteradores são "estritamente ordenados" (supondo que esse seja o termo certo), mas os iteráveis não.
JLRishe
3
@JLRishe Acho que você está certo, são de fato os iteradores que são pedidos - os iteráveis ​​não.
Benjamin Gruenbaum
8
Vale a pena notar que as promessas não acorrentam. Embora você obtenha a resolução na mesma ordem, não há garantia de quando as promessas são cumpridas. Em outras palavras, Promise.allnão pode ser usado para executar uma série de promessas em ordem, uma após a outra. As promessas carregadas no iterador precisam ser independentes uma da outra para que isso funcione previsivelmente.
Andrew Eddie
49

Como as respostas anteriores já declararam, Promise.allagrega todos os valores resolvidos com uma matriz correspondente à ordem de entrada das promessas originais (consulte Agregando promessas ).

No entanto, gostaria de salientar que o pedido é preservado apenas no lado do cliente!

Para o desenvolvedor, parece que as promessas foram cumpridas em ordem, mas, na realidade, as promessas são processadas em velocidades diferentes. É importante saber quando você trabalha com um back-end remoto, pois o back-end pode receber suas promessas em uma ordem diferente.

Aqui está um exemplo que demonstra o problema usando tempos limite:

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)

No código mostrado acima, três promessas (A, B, C) são dadas Promise.all. As três promessas são executadas em velocidades diferentes (C sendo a mais rápida e B sendo a mais lenta). É por isso que as console.logdeclarações das promessas aparecem nesta ordem:

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

Se as Promessas forem chamadas AJAX, um back-end remoto receberá esses valores nesta ordem. Mas no lado do cliente Promise.allgarante que os resultados sejam ordenados de acordo com as posições originais da myPromisesmatriz. É por isso que o resultado final é:

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

Se você deseja garantir também a execução real de suas promessas, precisará de um conceito como uma fila de promessas. Aqui está um exemplo usando a fila p (tenha cuidado, você precisa agrupar todas as promessas em funções):

Fila de Promessa Sequencial

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
fonte
2
grande resposta, expecially usando PQueue
ironstein
Preciso de uma fila seqüencial de promessas, mas como fazer isso se for necessário fazê-lo a partir de um resultado de registros sql? em um para? while?, nenhuma alternativa no ES2017 nosso ES2018?
stackdave
PQueue me ajudou! Obrigado! :)
podeig 02/07
28

Sim, os valores em resultsestão na mesma ordem que o promises.

Pode-se citar a especificaçãoPromise.all do ES6 , embora seja um pouco complicada devido à API do iterador usada e ao construtor de promessa genérico. No entanto, você notará que cada retorno de chamada do resolvedor possui um [[index]]atributo que é criado na iteração da matriz de promessas e usado para definir os valores na matriz de resultados.

Bergi
fonte
Estranho, vi hoje um vídeo do youtube que dizia que a ordem de saída é determinada pelo primeiro que resolveu, depois pelo segundo e depois ... Acho que o OP do vídeo estava errado?
Royi Namir
1
@RoyiNamir: Aparentemente ele era.
21415 Bergi
@Ozil Wat? A ordem cronológica da resolução não importa absolutamente quando todas as promessas são cumpridas. A ordem dos valores na matriz de resultados é a mesma que na matriz de entrada de promessas. Caso contrário, mude para uma implementação de promessa adequada.
Bergi 21/09/17