forEach/map et async ne vont pas de pair

Parfois, nous avons souvent tendance à passer des fonctions asynchrones avec les fonctions forEach et map.
Par exemple :
[1,2,3,4,5].forEach(async (n) => setTimeout(console.log(n), 1000));
Résultat : Tous les nombres de 1 à 5 sont imprimés les uns après les autres, mais il n'y a pas d'intervalle de 1 seconde après l'impression de chaque ligne.
Pourquoi cela arrive-t-il?
Regardez cette fonction javascipt :
async function doubleOf(n) {
return n*2;
}
Résultat : Il s'agit en fait de renvoyer une promesse qui se résout en un nombre.
Si nous écrivons un équivalent Typescript de cette fonction avec des types stricts, cela clarifiera les choses.
Le code suivant ne compilera pas :
async function doubleOf(n: number): number {
return n*2;
}
La bonne version serait :
async function doubleOf(n: number): Promise<number> {
return n*2;
}
Ne vous laissez pas berner par le sucre syntaxique fourni par async-wait. Si nous écrivions des promesses pures sans utiliser async, la fonction ci-dessus ressemblerait à :
function doubleOf(n) {
return new Promise((resolve) => resolve(n*2));
}
function doubleOf(n: number): Promise<number> {
return new Promise((resolve) => resolve(n*2));
}

- Nous avons
doubleOf
une fonction qui prend un nombre et renvoie un nombre. Vieux javascript simple. - Nous avons
doubleOfOldWay
une fonction qui prend un nombre et renvoie une promesse qui se résout en un nombre. - Nous avons
doubleOfNewWay
, une fonction asynchrone qui prend un nombre et semble renvoyer un nombre, mais elle renvoie en fait une promesse qui se résout en un nombre tout commedoubleOfOldWay
la fonction. doubleOfOldWay
etdoubleOfNewWay
les fonctions sont exactement les mêmes.- Et par conséquent, lorsque nous essayons d'exécuter une opération de multiplication sur les valeurs renvoyées par les fonctions
doubleOfOldWay
etdoubleOfNewWay
, le résultat étaitNaN
, car nous ne pouvons pas multiplier les promesses (évidemment !). - Multiplier
doubleOfOldWay
etdoubleOfNewWay
:
Revenons donc à notre exemple initial :
[1,2,3,4,5].forEach(async (n) => setTimeout(console.log(n), 1000));
La manière la plus correcte d'implémenter ce que nous attendons de cette fonction forEach consiste à utiliser une simple boucle for :
for(const number of [1,2,3,4,5]) {
console.log(number);
await new Promise(resolve => setTimeout(resolve, 1000)); // Sleep for "atleast" 1 second
}
[1,2,3,4,5].map(async (n) => n*2);
(5) [Promise, Promise, Promise, Promise, Promise]
0: Promise {<fulfilled>: 2}
1: Promise {<fulfilled>: 4}
2: Promise {<fulfilled>: 6}
3: Promise {<fulfilled>: 8}
4: Promise {<fulfilled>: 10}
Pour obtenir une liste des doubles de chaque nombre, ce que nous pouvons faire est :
await Promise.all([1,2,3,4,5].map(async (n) => n*2));
[2, 4, 6, 8, 10]