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

May 07 2023
Às vezes, tendemos a passar funções assíncronas com funções forEach e map. Por exemplo: Resultado: Todos os números a partir de 1.
Cabeçalho

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

funções assíncronas retornam promessas e não valores
  1. Temos doubleOfuma função que recebe um número e retorna um número. Javascript simples e antigo.
  2. Temos doubleOfOldWayuma função que recebe um número e retorna uma promessa que resolve um número.
  3. 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 a doubleOfOldWayfunção.
  4. doubleOfOldWaye doubleOfNewWayas funções são exatamente as mesmas.
  5. E, portanto, quando tentamos executar uma operação de multiplicação em valores retornados por doubleOfOldWaye doubleOfNewWayfunções, o resultado foi NaN, pois não podemos múltiplas promessas (obviamente!).
  6. Para multiplicar doubleOfOldWaye doubleOfNewWay:

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]