Użyj async await z Array.map

170

Biorąc pod uwagę następujący kod:

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

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

co powoduje następujący błąd:

TS2322: Typu „Promise <numer> []” nie można przypisać do typu „numer []”. Typu „Promise <number> nie można przypisać do typu„ number ”.

Jak mogę to naprawić? Jak mogę tworzyć async awaiti Array.mapwspółpracować?

Alon
źródło
6
Dlaczego próbujesz przekształcić operację synchroniczną w operację asynchroniczną? arr.map()jest synchroniczny i nie zwraca obietnicy.
jfriend00
2
Nie można wysłać operacji asynchronicznej do funkcji, takiej jak map, która oczekuje operacji synchronicznej i oczekuje, że zadziała.
Heretic Monkey
1
@ jfriend00 Mam wiele instrukcji await w funkcji wewnętrznej. W rzeczywistości jest to długa funkcja i właśnie ją uprościłem, aby była czytelna. Dodałem teraz wezwanie oczekujące, aby wyjaśnić, dlaczego powinien być asynchroniczny.
Alon,
Musisz czekać na coś, co zwraca obietnicę, a nie coś, co zwraca tablicę.
jfriend00
2
Warto zdać sobie sprawę, że za każdym razem, gdy oznaczysz funkcję jako async, sprawiasz, że funkcja zwraca obietnicę. Więc oczywiście mapa asynchronii zwraca tablicę obietnic :)
Anthony Manning-Franklin

Odpowiedzi:

380

Problem polega na tym, że próbujesz awaitwypełnić szereg obietnic, a nie obietnicę. To nie robi tego, czego oczekujesz.

Gdy obiekt przekazany do awaitnie jest obietnicą, awaitpo prostu zwraca wartość bez zmian, zamiast próbować ją rozwiązać. Więc ponieważ przekazałeś tutaj awaittablicę (obiektów Promise) zamiast Promise, wartość zwracana przez await jest po prostu tablicą, która jest typu Promise<number>[].

To, co musisz tutaj zrobić, to wywołanie Promise.alltablicy zwróconej przez mapw celu przekonwertowania jej na pojedynczą obietnicę przed awaitjej rozpoczęciem .

Według dokumentów MDN dlaPromise.all :

Promise.all(iterable)Metoda zwraca obietnicę to rozwiązanie, gdy wszystkie obietnice w iterowalny argumentu zostały rozwiązane, lub odrzuca ze względu na pierwszy przeszedł obietnicy, która odrzuca.

Więc w twoim przypadku:

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

To rozwiąże konkretny błąd, który napotkasz tutaj.

Ajedi32
źródło
1
Co :oznaczają dwukropki?
Daniel mówi Przywróć Monikę
11
@DanielPendergast Dotyczy adnotacji typu w TypeScript.
Ajedi32
Jaka jest różnica między wywołaniem callAsynchronousOperation(item);z awaitfunkcją mapy asynchronicznej i bez niej?
nerdizzle
@nerdizzle To brzmi jak dobry kandydat na inne pytanie. Zasadniczo jednak awaitfunkcja będzie czekać na zakończenie (lub niepowodzenie) operacji asynchronicznej przed kontynuowaniem, w przeciwnym razie będzie kontynuowana natychmiast bez czekania.
Ajedi32
@ Ajedi32 thx za odpowiedź. Ale bez oczekiwania na mapie asynchronicznej nie można już czekać na wynik funkcji?
nerdizzle
15

Jest na to inne rozwiązanie, jeśli nie używasz natywnych obietnic, ale Bluebird.

Możesz także spróbować użyć Promise.map () , mieszając array.map i Promise.all

W twoim przypadku:

  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
źródło
2
Jest inaczej - nie wykonuje wszystkich operacji równolegle, ale wykonuje je po kolei.
Andrey Tserkus
5
@AndreyTserkus Promise.mapSerieslub Promise.eachsą sekwencyjne, Promise.mapuruchamia je wszystkie naraz.
Kiechlus
1
@AndreyTserkus możesz uruchomić wszystkie lub niektóre operacje równolegle, zapewniając concurrencyopcję.
11
Warto wspomnieć, że nie jest to zwykły JS.
Michał
@Michal tak, to jest SyntaxError
CS QGB,
6

Jeśli odwzorujesz na tablicę obietnic, możesz następnie przekształcić je wszystkie w tablicę liczb. Zobacz Promise.all .

Dan Beaulieu
źródło
2

Zalecałbym użycie Promise.all, jak wspomniano powyżej, ale jeśli naprawdę chcesz uniknąć tego podejścia, możesz wykonać pętlę for lub inną:

const arr = [1,2,3,4,5];
let resultingArr = [];
for (let i in arr){
  await callAsynchronousOperation(i);
  resultingArr.push(i + 1)
}
Beckster
źródło
6
Promise.all będzie asynchroniczny dla każdego elementu tablicy. To będzie synchronizacja, aby rozpocząć następny, trzeba będzie poczekać na zakończenie jednego elementu.
Santiago Mendoza Ramirez
Dla tych, którzy próbują tego podejścia, zauważ, że for..of jest właściwym sposobem iteracji zawartości tablicy, podczas gdy for..in wykonuje iterację po indeksach.
ralfoide
2

Rozwiązanie poniżej, aby asynchronicznie przetwarzać wszystkie elementy tablicy ORAZ zachować kolejność:

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

Również codepen .

Zauważ, że „czekamy” tylko na Promise.all. Wielokrotnie dzwonimy do calc bez „czekania” i od razu zbieramy szereg nierozwiązanych obietnic. Następnie Promise.all czeka na rozwiązanie wszystkich z nich i zwraca w kolejności tablicę z rozwiązanymi wartościami.

Miki
źródło