Verwenden Sie async await mit Array.map

170

Gegeben den folgenden Code:

var arr = [1,2,3,4,5];

var results: number[] = await arr.map(async (item): Promise<number> => {
        await callAsynchronousOperation(item);
        return item + 1;
    });

Dies führt zu folgendem Fehler:

TS2322: Der Typ 'Versprechen <Nummer> []' kann nicht dem Typ 'Nummer []' zugewiesen werden. Der Typ 'Versprechen <Nummer> kann nicht dem Typ' Nummer 'zugewiesen werden.

Wie kann ich es reparieren? Wie kann ich machen async awaitund Array.mapzusammen arbeiten?

Alon
quelle
6
Warum versuchen Sie, eine synchrone Operation in eine asynchrone Operation umzuwandeln? arr.map()ist synchron und gibt kein Versprechen zurück.
jfriend00
2
Sie können keine asynchrone Operation an eine Funktion senden map, die eine synchrone erwartet und erwartet, dass sie funktioniert.
Heretic Monkey
1
@ jfriend00 Ich habe viele erwartete Aussagen in der inneren Funktion. Es ist eigentlich eine lange Funktion und ich habe sie nur vereinfacht, um sie lesbar zu machen. Ich habe jetzt einen Warteanruf hinzugefügt, um klarer zu machen, warum es asynchron sein sollte.
Alon
Sie müssen auf etwas warten, das ein Versprechen zurückgibt, nicht auf etwas, das ein Array zurückgibt.
jfriend00
1
Es ist nützlich zu erkennen, dass jedes Mal, wenn Sie eine Funktion als markieren async, diese Funktion ein Versprechen zurückgibt. Natürlich gibt eine Karte von Async eine Reihe von Versprechungen zurück :)
Anthony Manning-Franklin

Antworten:

379

Das Problem hierbei ist, dass Sie awaiteher eine Reihe von Versprechungen als ein Versprechen versuchen . Dies macht nicht das, was Sie erwarten.

Wenn das übergebene Objekt awaitkein Versprechen ist, wird awaitder Wert sofort unverändert zurückgegeben, anstatt zu versuchen, ihn aufzulösen. Da Sie awaithier also anstelle eines Versprechens ein Array (von Promise-Objekten) übergeben haben, ist der von await zurückgegebene Wert einfach das Array vom Typ Promise<number>[].

Was Sie hier tun müssen, ist Promise.alldas von zurückgegebene Array aufzurufen map, um es in ein einzelnes Versprechen umzuwandeln, bevor awaitSie es ausführen .

Gemäß den MDN-Dokumenten fürPromise.all :

Die Promise.all(iterable)Methode gibt ein Versprechen zurück, das aufgelöst wird, wenn alle Versprechen im iterierbaren Argument aufgelöst wurden, oder lehnt es mit dem Grund des ersten übergebenen Versprechens ab, das abgelehnt wird.

Also in deinem Fall:

var arr = [1, 2, 3, 4, 5];

var results: number[] = await Promise.all(arr.map(async (item): Promise<number> => {
    await callAsynchronousOperation(item);
    return item + 1;
}));

Dadurch wird der spezifische Fehler behoben, auf den Sie hier stoßen.

Ajedi32
quelle
1
Was bedeuten die :Doppelpunkte?
Daniel sagt Reinstate Monica
11
@DanielPendergast Es ist für Typanmerkungen in TypeScript.
Ajedi32
Was ist der Unterschied zwischen dem Aufrufen callAsynchronousOperation(item);mit und ohne awaitinnerhalb der asynchronen Kartenfunktion?
Nerdizzle
@nerdizzle Das klingt nach einem guten Kandidaten für eine andere Frage. Grundsätzlich awaitwartet die Funktion jedoch, bis der asynchrone Vorgang abgeschlossen ist (oder fehlschlägt), bevor sie fortgesetzt wird. Andernfalls wird sie sofort fortgesetzt, ohne zu warten.
Ajedi32
@ Ajedi32 Danke für die Antwort. Aber ohne das Warten in der asynchronen Karte ist es nicht mehr möglich, das Ergebnis der Funktion abzuwarten?
Nerdizzle
15

Es gibt eine andere Lösung dafür, wenn Sie keine nativen Versprechen verwenden, sondern Bluebird.

Sie können auch versuchen, Promise.map () zu verwenden , indem Sie array.map und Promise.all mischen

In Ihrem Fall:

  var arr = [1,2,3,4,5];

  var results: number[] = await Promise.map(arr, async (item): Promise<number> => {
    await callAsynchronousOperation(item);
    return item + 1;
  });
Gabriel Cheung
quelle
2
Es ist anders - es werden nicht alle Operationen parallel ausgeführt, sondern nacheinander ausgeführt.
Andrey Tserkus
5
@AndreyTserkus Promise.mapSeriesoder Promise.eachsind sequenziell, Promise.mapstartet sie alle auf einmal.
Kiechlus
1
@AndreyTserkus Sie können alle oder einige Vorgänge parallel ausführen, indem Sie die concurrencyOption angeben .
11
Es ist erwähnenswert, dass es kein Vanille-JS ist.
Michal
@ Michael ja, es ist SyntaxError
CS QGB
6

Wenn Sie einem Array von Versprechungen zuordnen, können Sie sie alle in ein Array von Zahlen auflösen. Siehe Promise.all .

Dan Beaulieu
quelle
2

Ich würde empfehlen, Promise.all wie oben erwähnt zu verwenden, aber wenn Sie diesen Ansatz wirklich vermeiden möchten, können Sie eine for- oder eine andere Schleife ausführen:

const arr = [1,2,3,4,5];
let resultingArr = [];
for (let i in arr){
  await callAsynchronousOperation(i);
  resultingArr.push(i + 1)
}
Beckster
quelle
6
Promise.all ist für jedes Element des Arrays asynchron. Dies ist eine Synchronisierung. Es muss warten, bis ein Element fertig ist, um das nächste zu starten.
Santiago Mendoza Ramirez
Beachten Sie für diejenigen, die diesen Ansatz ausprobieren, dass for..of der richtige Weg ist, um einen Array-Inhalt zu iterieren, während for..in über die Indizes iteriert.
Ralfoide
2

Lösung unten, um alle Elemente eines Arrays asynchron zu verarbeiten UND die Reihenfolge beizubehalten:

const arr = [1, 2, 3, 4, 5, 6, 7, 8];
const randomDelay = () => new Promise(resolve => setTimeout(resolve, Math.random() * 1000));

const calc = async n => {
  await randomDelay();
  return n * 2;
};

const asyncFunc = async () => {
  const unresolvedPromises = arr.map(n => calc(n));
  const results = await Promise.all(unresolvedPromises);
};

asyncFunc();

Auch Codepen .

Beachten Sie, dass wir nur auf Promise.all "warten". Wir rufen calc an, ohne mehrmals zu warten, und sammeln sofort eine Reihe ungelöster Versprechen. Dann wartet Promise.all auf die Auflösung aller und gibt ein Array mit den aufgelösten Werten in der angegebenen Reihenfolge zurück.

Miki
quelle