Promise.all: Reihenfolge der aufgelösten Werte

187

Wenn man sich MDN ansieht, sieht es so aus, als ob der valuesan den then()Rückruf von Promise.all übergebene Wert die Werte in der Reihenfolge der Versprechen enthält. Beispielsweise:

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

Kann jemand eine Spezifikation zitieren, in der angegeben ist, in welcher Reihenfolge sie valuessein soll?

PS: Das Ausführen eines solchen Codes hat gezeigt, dass dies wahr zu sein scheint, obwohl dies natürlich kein Beweis ist - es könnte Zufall gewesen sein.

Thorben Croisé
quelle

Antworten:

270

In Kürze bleibt die Reihenfolge erhalten .

Wenn Sie der von Ihnen verknüpften Spezifikation folgen, Promise.all(iterable)wird ein iterable(dh ein Objekt, das die IteratorSchnittstelle unterstützt ) als Parameter verwendet und später PerformPromiseAll( iterator, constructor, resultCapability)mit ihm aufgerufen, wobei letzterer die iterableVerwendung durchläuft IteratorStep(iterator).
Dies bedeutet, dass wenn das iterable, an das Sie übergeben, Promise.all()streng geordnet ist, es nach dem Übergeben immer noch bestellt wird.

Die Auflösung wird dort implementiert, Promise.all() Resolvewo jedes aufgelöste Versprechen einen internen [[Index]]Steckplatz hat, der den Index des Versprechens in der ursprünglichen Eingabe markiert.


All dies bedeutet, dass die Ausgabe streng als Eingabe geordnet ist, solange die Eingabe streng geordnet ist (z. B. ein Array).

Sie können dies in der folgenden Geige (ES6) in Aktion sehen:

// 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
quelle
1
Wie würde ein iterable nicht streng geordnet werden? Jedes iterable wird "streng geordnet" durch die Reihenfolge, in der es seine Werte erzeugt.
Benjamin Gruenbaum
Hinweis - Firefox ist der einzige Browser, der iterables in Versprechungen korrekt implementiert. Chrome wird derzeit throweine Ausnahme sein, wenn Sie eine iterable an übergeben Promise.all. Außerdem ist mir keine Implementierung von Userland-Versprechungen bekannt, die derzeit das Übergeben von Iterables unterstützt, obwohl viele zu diesem Zeitpunkt darüber debattiert und sich dagegen entschieden haben.
Benjamin Gruenbaum
3
@BenjaminGruenbaum Ist es nicht möglich, eine Iterable zu haben, die bei zweimaliger Iteration zwei verschiedene Ordnungen erzeugt? Zum Beispiel ein Kartenspiel, das Karten in zufälliger Reihenfolge erzeugt, wenn es iteriert wird? Ich weiß nicht, ob "streng geordnet" hier die richtige Terminologie ist, aber nicht alle Iterables haben eine feste Reihenfolge. Ich denke, es ist vernünftig zu sagen, dass Iteratoren "streng geordnet" sind (vorausgesetzt, das ist der richtige Begriff), iterable jedoch nicht.
JLRishe
3
@JLRishe Ich denke du hast recht, es sind tatsächlich Iteratoren, die geordnet sind - iterables nicht.
Benjamin Gruenbaum
8
Es ist erwähnenswert, dass die Versprechen nicht verketten. Obwohl Sie die Auflösung in derselben Reihenfolge erhalten, gibt es keine Garantie dafür, wann die Versprechen eingehalten werden. Mit anderen Worten, Promise.allkann nicht verwendet werden, um eine Reihe von Versprechungen nacheinander auszuführen. Die in den Iterator geladenen Versprechen müssen unabhängig voneinander sein, damit dies vorhersehbar funktioniert.
Andrew Eddie
46

Wie in den vorherigen Antworten bereits angegeben, werden Promise.allalle aufgelösten Werte mit einem Array aggregiert, das der Eingabereihenfolge der ursprünglichen Versprechen entspricht (siehe Aggregieren von Versprechen ).

Ich möchte jedoch darauf hinweisen, dass die Bestellung nur auf Kundenseite erhalten bleibt!

Für den Entwickler sieht es so aus, als ob die Versprechen in der richtigen Reihenfolge erfüllt wurden, aber in Wirklichkeit werden die Versprechen mit unterschiedlichen Geschwindigkeiten verarbeitet. Dies ist wichtig zu wissen, wenn Sie mit einem Remote-Backend arbeiten, da das Backend Ihre Versprechen möglicherweise in einer anderen Reihenfolge erhält.

Hier ist ein Beispiel, das das Problem anhand von Zeitüberschreitungen demonstriert:

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)

In dem oben gezeigten Code werden drei Versprechen (A, B, C) gegeben Promise.all. Die drei Versprechen werden mit unterschiedlichen Geschwindigkeiten ausgeführt (C ist die schnellste und B die langsamste). Deshalb erscheinen die console.logAussagen der Versprechen in dieser Reihenfolge:

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

Wenn es sich bei den Versprechungen um AJAX-Aufrufe handelt, erhält ein Remote-Backend diese Werte in dieser Reihenfolge. Auf der Client-Seite wird jedoch Promise.allsichergestellt, dass die Ergebnisse gemäß den ursprünglichen Positionen des myPromisesArrays sortiert werden . Deshalb ist das Endergebnis:

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

Wenn Sie auch die tatsächliche Ausführung Ihrer Versprechen garantieren möchten, benötigen Sie ein Konzept wie eine Versprechen-Warteschlange. Hier ist ein Beispiel für die Verwendung der p-Warteschlange (Vorsicht, Sie müssen alle Versprechen in Funktionen einschließen):

Sequentielle Versprechenswarteschlange

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

Ergebnis

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

['A (slow)', 'B (slower)', 'C (fast)']
Benny Neugebauer
quelle
2
Tolle Antwort, besonders mit PQueue
Ironstein
Ich benötige eine sequentielle Versprechenswarteschlange, aber wie geht das, wenn ich dies anhand von SQL-Ergebnisdatensätzen tun muss? in einem für? während? keine Alternative in ES2017 unsere ES2018?
Stackdave
27

Ja, die Werte in resultssind in derselben Reihenfolge wie die promises.

Man könnte die ES6-SpezifikationPromise.all zitieren , obwohl sie aufgrund der verwendeten Iterator-API und des generischen Versprechen-Konstruktors etwas kompliziert ist. Sie werden jedoch feststellen, dass jeder Resolver-Rückruf eine hat[[index]] Attribut hat, das in der Versprechen-Array-Iteration erstellt und zum Festlegen der Werte im Ergebnis-Array verwendet wird.

Bergi
quelle
Seltsam, ich habe heute ein Youtube-Video gesehen, das besagt, dass die Ausgabereihenfolge von dem ersten bestimmt wird, der das Problem gelöst hat, dann vom zweiten, dann ... Ich denke, das Video-OP war falsch?
Royi Namir
1
@ RoyiNamir: Anscheinend war er.
Bergi
@Ozil Wat? Die chronologische Reihenfolge der Auflösung spielt absolut keine Rolle, wenn alle Versprechen erfüllt sind. Die Reihenfolge der Werte im Ergebnisarray ist dieselbe wie im Eingabearray der Versprechen. Wenn dies nicht der Fall ist, sollten Sie zu einer ordnungsgemäßen Implementierung von Versprechungen wechseln.
Bergi