forEach/map y async no van de la mano

A veces, solemos pasar funciones asíncronas con funciones forEach y map.
Por ejemplo:
[1,2,3,4,5].forEach(async (n) => setTimeout(console.log(n), 1000));
Resultado: Todos los números del 1 al 5 se imprimen uno tras otro, pero no hay un intervalo de 1 segundo después de imprimir cada línea.
¿Por qué pasó esto?
Mira esta función javascipt:
async function doubleOf(n) {
return n*2;
}
Resultado: Esto en realidad está devolviendo una Promesa que se resuelve en un número.
Si escribimos un equivalente de TypeScript de esta función con tipos estrictos, aclarará las cosas.
El siguiente código no compilará:
async function doubleOf(n: number): number {
return n*2;
}
La versión correcta sería:
async function doubleOf(n: number): Promise<number> {
return n*2;
}
No se deje engañar por el azúcar sintáctico proporcionado por async-await. Si escribimos promesas puras sin usar async, la función anterior se vería así:
function doubleOf(n) {
return new Promise((resolve) => resolve(n*2));
}
function doubleOf(n: number): Promise<number> {
return new Promise((resolve) => resolve(n*2));
}

- Tenemos
doubleOf
una función que toma un número y devuelve un número. Javascript simple y antiguo. - Tenemos
doubleOfOldWay
una función que toma un número y devuelve una promesa que se resuelve en un número. - Tenemos
doubleOfNewWay
, una función asíncrona que toma un número y parece que devuelve un número, pero en realidad devuelve una promesa que se resuelve en un número como ladoubleOfOldWay
función. doubleOfOldWay
ydoubleOfNewWay
las funciones son exactamente las mismas.- Y por lo tanto, cuando tratamos de ejecutar una operación de multiplicación en los valores devueltos por
doubleOfOldWay
ydoubleOfNewWay
funciones, el resultado fueNaN
, ya que no podemos multiplicar las promesas (¡obviamente!). - Para multiplicar
doubleOfOldWay
ydoubleOfNewWay
:
Volvamos a nuestro ejemplo inicial:
[1,2,3,4,5].forEach(async (n) => setTimeout(console.log(n), 1000));
La forma más correcta de implementar lo que esperamos de esta función forEach es usando un bucle for simple:
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}
Para obtener una lista del doble de cada número, lo que podemos hacer es:
await Promise.all([1,2,3,4,5].map(async (n) => n*2));
[2, 4, 6, 8, 10]