Promise.all: Kolejność ustalonych wartości

189

Patrząc na MDN wygląda na to, że valuesprzekazany do then()wywołania zwrotnego Promise.all zawiera wartości w kolejności obietnic. Na przykład:

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

Czy ktoś może podać specyfikację określającą, w jakiej kolejności valuespowinna być?

PS: Uruchomienie takiego kodu pokazało, że to prawda, chociaż oczywiście nie jest to dowód - mógł to być zbieg okoliczności.

Thorben Croisé
źródło

Odpowiedzi:

274

Wkrótce kolejność zostanie zachowana .

Zgodnie ze specyfikacją, z którą się łączysz, Promise.all(iterable)przyjmuje parametr iterable(to znaczy obiekt obsługujący Iteratorinterfejs) jako parametr, a następnie wywołuje PerformPromiseAll( iterator, constructor, resultCapability)z nim wywołania , przy czym ten drugi iterableużywa pętli IteratorStep(iterator).
Oznacza to, że jeśli jeśli iterowalność, którą przekazujesz, Promise.all()jest ściśle uporządkowana, to nadal zostanie zamówiona po przekazaniu.

Rozwiązanie jest realizowane za pośrednictwem miejsca, w Promise.all() Resolvektórym każda rozwiązana obietnica ma wewnętrzne [[Index]]gniazdo, które oznacza indeks obietnicy na oryginalnym wejściu.


Wszystko to oznacza, że ​​dane wyjściowe są ściśle uporządkowane jako dane wejściowe, o ile dane wejściowe są ściśle uporządkowane (na przykład tablica).

Możesz to zobaczyć w akcji w poniższym skrzypcach (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));
});

Gnida
źródło
1
W jaki sposób iterowalność nie byłaby ściśle uporządkowana? Każda iterowalna jest „ściśle uporządkowana” według kolejności, w której generuje swoje wartości.
Benjamin Gruenbaum
Uwaga - Firefox jest jedyną przeglądarką, która poprawnie implementuje iteratory w obietnicach. Chrome będzie obecnie throwwyjątkowy, jeśli prześlesz iterowalny do Promise.all. Ponadto nie znam żadnych implementacji obietnic użytkowników, które obecnie wspierają przekazywanie iteracji, chociaż wielu debatowało nad tym i nie zdecydowało się na to.
Benjamin Gruenbaum
3
@BenjaminGruenbaum Czy nie jest możliwe, aby iterowalność generowała dwa różne zamówienia po dwukrotnym powtórzeniu? Na przykład, talia kart, która produkuje karty w losowej kolejności, gdy jest iterowana? Nie wiem, czy „ściśle uporządkowana” jest tutaj właściwą terminologią, ale nie wszystkie iterery mają ustaloną kolejność. Więc myślę, że to rozsądne, aby powiedzieć, że iteratory są „ściśle uporządkowane” (zakładając, że to właściwy termin), ale iterables nie są.
JLRishe
3
@JLRishe Chyba masz rację, to rzeczywiście iteratory są uporządkowane - iteratory nie są.
Benjamin Gruenbaum
8
Warto zauważyć, że obietnice się nie łączą. Mimo że uzyskasz rozdzielczość w tej samej kolejności, nie ma gwarancji, że dotrzymane zostaną obietnice. Innymi słowy, Promise.allnie można użyć do uruchomienia szeregu obietnic w kolejności, jedna po drugiej. Obietnice załadowane do iteratora muszą być od siebie niezależne, aby działało przewidywalnie.
Andrew Eddie,
49

Jak już wspomniano w poprzednich odpowiedziach, Promise.allagreguje wszystkie rozstrzygnięte wartości za pomocą tablicy odpowiadającej kolejności wprowadzania oryginalnych obietnic (patrz Agregowanie obietnic ).

Chciałbym jednak zaznaczyć, że zamówienie jest zachowywane tylko po stronie klienta!

Dla programisty wygląda na to, że obietnice zostały spełnione w kolejności, ale w rzeczywistości obietnice są przetwarzane z różnymi prędkościami. Jest to ważne, aby wiedzieć, kiedy pracujesz ze zdalnym backendem, ponieważ backend może otrzymywać Twoje obietnice w innej kolejności.

Oto przykład, który pokazuje problem przy użyciu limitów czasu:

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)

W powyższym kodzie podane są trzy obietnice (A, B, C) Promise.all. Trzy obietnice wykonują się przy różnych prędkościach (C jest najszybszy, a B jest najwolniejszy). Dlatego console.logoświadczenia Obietnic pojawiają się w następującej kolejności:

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

Jeśli obietnice są wywołaniami AJAX, zdalny backend otrzyma te wartości w tej kolejności. Ale po stronie klienta Promise.allzapewnia, że ​​wyniki są uporządkowane zgodnie z pierwotnymi pozycjami myPromisestablicy. Właśnie dlatego końcowy wynik to:

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

Jeśli chcesz zagwarantować również faktyczne wykonanie Twoich obietnic, potrzebujesz koncepcji takiej jak kolejka Obietnic. Oto przykład użycia kolejki p (uważaj, musisz zawinąć wszystkie obietnice w funkcje):

Sekwencyjna kolejka obietnic

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

Wynik

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

['A (slow)', 'B (slower)', 'C (fast)']
Benny Neugebauer
źródło
2
świetna odpowiedź, szczególnie przy użyciu PQueue
ironstein
Potrzebuję kolejki z obietnicą sekwencyjną, ale jak to zrobić, jeśli muszę to zrobić z rekordów sql wyniku? w za? podczas gdy?, nie ma alternatywy w ES2017 nasz ES2018?
stackdave
PQueue pomógł mi! Dziękuję Ci! :)
podeig
28

Tak, wartości w resultssą w tej samej kolejności co promises.

Można zacytować specyfikację ES6Promise.all , choć jest nieco skomplikowana ze względu na zastosowany interfejs iteratora i ogólny konstruktor obietnic. Jednak zauważysz, że każde wywołanie zwrotne resolvera ma [[index]]atrybut, który jest tworzony w iteracji tablicy obietnic i używany do ustawiania wartości w tablicy wyników.

Bergi
źródło
Dziwne, widziałem dzisiaj film na youtube, który powiedział, że kolejność wyjściowa jest określana przez pierwszego, który rozwiązał, a następnie drugi, a następnie ... Myślę, że OP wideo był nieprawidłowy?
Royi Namir,
1
@RoyiNamir: Najwyraźniej był.
Bergi,
@Ozil Wat? Chronologiczny porządek rozdzielczości absolutnie nie ma znaczenia, kiedy wszystkie obietnice się spełnią. Kolejność wartości w tablicy wynikowej jest taka sama jak w tablicy wejściowej obietnic. Jeśli tak nie jest, powinieneś przejść do właściwej realizacji obietnicy.
Bergi,