Promise.all: ordine dei valori risolti


190

Guardando MDN sembra che il valuespassato al then()callback di Promise.all contenga i valori nell'ordine delle promesse. Per esempio:

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

Qualcuno può citare una specifica affermando in quale ordine valuesdovrebbe essere?

PS: L'esecuzione di un codice del genere ha mostrato che questo sembra essere vero anche se ovviamente non è una prova - potrebbe essere stata una coincidenza.

Risposte:


274

In breve, l'ordine viene preservato .

Seguendo le specifiche a cui si è collegati, Promise.all(iterable)prende un iterable(ovvero un oggetto che supporta l' Iteratorinterfaccia) come parametro e successivamente chiama PerformPromiseAll( iterator, constructor, resultCapability)con esso, in cui quest'ultimo passa sopra iterableusando IteratorStep(iterator).
Ciò significa che se l'iterabile in cui si passa Promise.all()è strettamente ordinato, verranno comunque ordinati una volta passati.

La risoluzione viene implementata tramite Promise.all() Resolvedove ogni promessa risolta ha uno [[Index]]slot interno , che segna l'indice della promessa nell'input originale.


Tutto ciò significa che l'output è rigorosamente ordinato come input fintanto che l'ingresso è rigorosamente ordinato (ad esempio un array).

Puoi vederlo in azione nel violino qui sotto (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));
});


1
In che modo un iterabile non può essere rigorosamente ordinato? Ogni iterabile è "rigorosamente ordinato" dall'ordine in cui produce i suoi valori.
Benjamin Gruenbaum,

Nota: Firefox è l'unico browser che implementa correttamente le iterabili nelle promesse. Chrome sarà attualmente throwun'eccezione se si passa a un iterabile a Promise.all. Inoltre, non sono a conoscenza di alcuna implementazione promessa da userland che attualmente supporta il passaggio di iterabili, anche se molti l'hanno discusso e deciso in quel momento.
Benjamin Gruenbaum,

3
@BenjaminGruenbaum Non è possibile avere un iterabile che produce due ordini diversi dopo essere stato ripetuto due volte? Ad esempio, un mazzo di carte che produce carte in ordine casuale quando viene ripetuto? Non so se "rigorosamente ordinato" è la terminologia giusta qui, ma non tutti gli iterabili hanno un ordine fisso. Quindi penso che sia ragionevole dire che gli iteratori sono "rigorosamente ordinati" (supponendo che sia il termine giusto), ma gli iterabili no.
JLRishe,

3
@JLRishe Immagino che tu abbia ragione, in effetti sono gli iteratori che vengono ordinati - gli iterabili no.
Benjamin Gruenbaum,

8
Vale la pena notare che le promesse non si incatenano. Mentre otterrai la risoluzione nello stesso ordine, non ci sono garanzie su quando le promesse vengono mantenute. In altre parole, Promise.allnon può essere utilizzato per eseguire una serie di promesse in ordine, una dopo l'altra. Le promesse caricate nell'iteratore devono essere indipendenti l'una dall'altra affinché ciò funzioni in modo prevedibile.
Andrew Eddie,

49

Come già indicato nelle risposte precedenti, Promise.allaggrega tutti i valori risolti con un array corrispondente all'ordine di input delle Promesse originali (vedere Aggregating Promises ).

Tuttavia, vorrei sottolineare che l'ordine è conservato solo dal lato client!

Per lo sviluppatore sembra che le Promesse siano state rispettate nell'ordine, ma in realtà le Promesse vengono elaborate a velocità diverse. Questo è importante sapere quando lavori con un back-end remoto perché il back-end potrebbe ricevere le tue promesse in un ordine diverso.

Ecco un esempio che dimostra il problema utilizzando i timeout:

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)

Nel codice mostrato sopra, vengono date tre Promesse (A, B, C) Promise.all. Le tre promesse vengono eseguite a velocità diverse (C è la più veloce e B la più lenta). Ecco perché le console.logdichiarazioni delle Promesse si presentano in questo ordine:

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

Se le promesse sono chiamate AJAX, un back-end remoto riceverà questi valori in questo ordine. Ma dal lato client Promise.allgarantisce che i risultati siano ordinati in base alle posizioni originali myPromisesdell'array. Ecco perché il risultato finale è:

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

Se vuoi garantire anche l'effettiva esecuzione delle tue Promesse, allora avrai bisogno di un concetto come una coda di Promessa. Ecco un esempio usando la coda-p (attenzione, è necessario racchiudere tutte le promesse nelle funzioni):

Coda di promessa sequenziale

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

Risultato

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

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

2
ottima risposta, specialmente usando PQueue
ironstein l'

Ho bisogno di una coda di promessa sequenziale, ma come faccio se devo farlo da un risultato sql records? in un per? mentre?, nessuna alternativa in ES2017 il nostro ES2018?
StackDave

PQueue mi ha aiutato! Grazie! :)
podeig

28

Sì, i valori in resultssono nello stesso ordine di promises.

Si potrebbe citare le specifiche ES6Promise.all , anche se è un po 'contorto a causa dell'API iteratore usato e del costruttore di promesse generiche. Tuttavia, noterai che ogni callback del resolver ha un [[index]]attributo che viene creato nell'iterazione della matrice promessa e utilizzato per impostare i valori sulla matrice dei risultati.


Strano, ho visto un video di YouTube oggi che diceva che l'ordine di uscita è determinato dal primo che si è risolto, poi dal secondo, quindi ..... Suppongo che il video OP sia sbagliato?
Royi Namir,

1
@RoyiNamir: Apparentemente lo era.
Bergi,

@Ozil Wat? L'ordine cronologico di risoluzione non ha assolutamente importanza quando tutte le promesse si realizzano. L'ordine dei valori nell'array dei risultati è lo stesso dell'array di input delle promesse. In caso contrario, dovresti passare a una corretta implementazione delle promesse.
Bergi,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.