forEach/map e async não andam de mãos dadas

Às vezes, tendemos a passar funções assíncronas com funções forEach e map.
Por exemplo:
[1,2,3,4,5].forEach(async (n) => setTimeout(console.log(n), 1000));
Resultado: Todos os números de 1 a 5 são impressos um após o outro, mas não há intervalo de 1 segundo após a impressão de cada linha.
Por que isso acontece?
Olhe para esta função javascipt:
async function doubleOf(n) {
return n*2;
}
Resultado: na verdade, está retornando uma promessa que resolve um número.
Se escrevermos um Typescript equivalente a esta função com tipos estritos, isso tornará as coisas mais claras.
O seguinte código não compila:
async function doubleOf(n: number): number {
return n*2;
}
A versão correta seria:
async function doubleOf(n: number): Promise<number> {
return n*2;
}
Não se deixe enganar pelo açúcar sintático fornecido pelo async-await. Se escrevêssemos promessas puras sem usar async, a função acima ficaria assim:
function doubleOf(n) {
return new Promise((resolve) => resolve(n*2));
}
function doubleOf(n: number): Promise<number> {
return new Promise((resolve) => resolve(n*2));
}

- Temos
doubleOf
uma função que recebe um número e retorna um número. Javascript simples e antigo. - Temos
doubleOfOldWay
uma função que recebe um número e retorna uma promessa que resolve um número. - Temos
doubleOfNewWay
, uma função assíncrona que recebe um número e parece que retorna um número, mas na verdade retorna uma promessa que resolve para um número exatamente como adoubleOfOldWay
função. doubleOfOldWay
edoubleOfNewWay
as funções são exatamente as mesmas.- E, portanto, quando tentamos executar uma operação de multiplicação em valores retornados por
doubleOfOldWay
edoubleOfNewWay
funções, o resultado foiNaN
, pois não podemos múltiplas promessas (obviamente!). - Para multiplicar
doubleOfOldWay
edoubleOfNewWay
:
Então, voltando ao nosso exemplo inicial:
[1,2,3,4,5].forEach(async (n) => setTimeout(console.log(n), 1000));
A maneira mais correta de implementar o que esperamos dessa função forEach é usando um loop for simples:
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 obter uma lista do dobro de cada número, o que podemos fazer é:
await Promise.all([1,2,3,4,5].map(async (n) => n*2));
[2, 4, 6, 8, 10]