Utilisez async await avec Array.map

171

Compte tenu du code suivant:

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

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

ce qui produit l'erreur suivante:

TS2322: Le type «Promise <numéro> []» ne peut pas être affecté au type «numéro []». Le type «Promise <numéro> ne peut pas être affecté au type« numéro ».

Comment puis-je y remédier? Comment puis-je créer async awaitet Array.maptravailler ensemble?

Alon
la source
6
Pourquoi essayez-vous de transformer une opération synchrone en une opération asynchrone? arr.map()est synchrone et ne renvoie pas de promesse.
jfriend00
2
Vous ne pouvez pas envoyer une opération asynchrone à une fonction, comme map, qui attend une opération synchrone et s'attendre à ce qu'elle fonctionne.
Heretic Monkey
1
@ jfriend00 J'ai de nombreuses instructions d'attente dans la fonction interne. C'est en fait une fonction longue et je l'ai simplement simplifiée pour la rendre lisible. J'ai maintenant ajouté un appel d'attente pour expliquer plus clairement pourquoi il devrait être asynchrone.
Alon
Vous devez attendre quelque chose qui renvoie une promesse, pas quelque chose qui renvoie un tableau.
jfriend00
2
Une chose utile à réaliser est que chaque fois que vous marquez une fonction comme async, vous faites en sorte que cette fonction renvoie une promesse. Alors bien sûr, une carte d'async renvoie un tableau de promesses :)
Anthony Manning-Franklin

Réponses:

382

Le problème ici est que vous essayez awaitun éventail de promesses plutôt qu'une promesse. Cela ne fait pas ce que vous attendez.

Lorsque l'objet passé à awaitn'est pas une promesse, awaitrenvoie simplement la valeur telle quelle immédiatement au lieu d'essayer de la résoudre. Donc, puisque vous avez passé awaitun tableau (d'objets Promise) ici au lieu d'une Promise, la valeur retournée par await est simplement ce tableau, qui est de typePromise<number>[] .

Ce que vous devez faire ici est d'appeler Promise.allle tableau renvoyé par mapafin de le convertir en une seule promesse avant de l' awaitengager.

Selon la documentation MDN pourPromise.all :

La Promise.all(iterable)méthode renvoie une promesse qui se résout lorsque toutes les promesses de l'argument itérable ont été résolues, ou rejette avec la raison de la première promesse passée qui rejette.

Donc dans votre cas:

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

Cela résoudra l'erreur spécifique que vous rencontrez ici.

Ajedi32
la source
1
Que :signifient les deux points?
Daniel dit réintégrer Monica
12
@DanielPendergast C'est pour les annotations de type dans TypeScript.
Ajedi32
Quelle est la différence entre un appel callAsynchronousOperation(item);avec et sans awaità l'intérieur de la fonction de carte asynchrone?
nerdizzle
@nerdizzle Cela semble être un bon candidat pour une autre question. En gros, awaitla fonction attendra que l'opération asynchrone se termine (ou échoue) avant de continuer, sinon elle continuera immédiatement sans attendre.
Ajedi32
@ Ajedi32 thx pour la réponse. Mais sans l'attente dans la carte asynchrone, il n'est plus possible d'attendre le résultat de la fonction?
nerdizzle
16

Il existe une autre solution pour cela si vous n'utilisez pas Native Promises mais Bluebird.

Vous pouvez également essayer d'utiliser Promise.map () , en mélangeant array.map et Promise.all

Dans votre cas:

  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
la source
2
C'est différent - il n'exécute pas toutes les opérations en parallèle, mais les exécute plutôt dans l'ordre.
Andrey Tserkus
5
@AndreyTserkus Promise.mapSeriesou Promise.eachsont séquentiels, les Promise.mapdémarre tous en même temps.
Kiechlus
1
@AndreyTserkus vous pouvez exécuter toutes ou certaines opérations en parallèle en fournissant l' concurrencyoption.
11
Il est à noter que ce n'est pas une JS vanille.
Michal
@Michal ouais, c'est SyntaxError
CS QGB
7

Si vous mappez à un tableau de promesses, vous pouvez alors les résoudre toutes en un tableau de nombres. Voir Promise.all .

Dan Beaulieu
la source
2

Je recommanderais d'utiliser Promise.all comme mentionné ci-dessus, mais si vous avez vraiment envie d'éviter cette approche, vous pouvez faire une boucle for ou toute autre boucle:

const arr = [1,2,3,4,5];
let resultingArr = [];
for (let i in arr){
  await callAsynchronousOperation(i);
  resultingArr.push(i + 1)
}
Beckster
la source
6
Le Promise.all sera asynchrone pour chaque élément du tableau. Ce sera une synchronisation, il faudra attendre la fin d'un élément pour démarrer le suivant.
Santiago Mendoza Ramirez
Pour ceux qui essaient cette approche, notez que for..of est la bonne façon d'itérer le contenu d'un tableau, tandis que for..in itère sur les indices.
ralfoide le
2

Solution ci-dessous pour traiter tous les éléments d'un tableau de manière asynchrone ET conserver l'ordre:

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

Codepen également .

Notez que nous n'attendons que Promise.all. Nous appelons calc sans "attendre" plusieurs fois, et nous collectons tout de suite un tableau de promesses non résolues. Puis Promise.all attend la résolution de tous et retourne un tableau avec les valeurs résolues dans l'ordre.

Miki
la source