Async/await no forEach: como conciliar assincronia com loops

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Por que seu async/await silencia no forEach? Entendendo de primeira vista

\n\n

Você já escreveu algo como este código, esperando que ele executasse operações assíncronas em ordem?

\n\n
const users = ['alice', 'bob', 'charlie'];\n\nusers.forEach(async (user) => {\n  const data = await fetch(`https://api.example.com/users/${user}`);\n  console.log(await data.json());\n});\n\nconsole.log('Pronto!');
\n\n

E recebeu "Pronto!" antes dos dados carregarem? Familiar? Você não está sozinho. Esta é uma das perguntas mais populares no Stack Overflow com milhares de visualizações. O problema é que forEach é um método síncrono. Ele apenas executa os callbacks, mas não espera que eles terminem. Suas funções async iniciam, mas o forEach já segue em frente, como um velocista que não se importa com a chegada dos outros.

\n\n

Neste artigo, não vamos apenas mostrar como corrigir isso. Vamos analisar por que isso acontece e dar a você três soluções funcionais com exemplos. Você aprenderá quando usar Promise.all e quando usar o bom e velho for...of. Vamos lá!

\n\n

Anatomia do problema: forEach e promessas

\n\n

Vamos dar uma olhada nos bastidores. O método forEach em JavaScript é estruturado assim:

\n\n
Array.prototype.forEach = function(callback) {\n  for (let i = 0; i < this.length; i++) {\n    callback(this[i], i, this);\n  }\n};
\n\n

Viu? Ele apenas chama o callback para cada elemento, mas não retorna nada. Mesmo que o callback retorne uma Promise, o forEach a ignora. Ele não espera, não coleta resultados — apenas executa e esquece.

\n\n

Quando você escreve async (user) => { ... }, sua função retorna uma Promise. Mas o forEach não sabe o que fazer com ela. No final, todas as requisições são iniciadas em paralelo (se for fetch), mas você não pode esperar que terminem. O código após o forEach é executado instantaneamente.

\n\n

Exemplo real: como isso parece na prática

\n\n

Imagine que você está processando um array de IDs de produtos e para cada um precisa obter o preço do banco de dados:

\n\n
const productIds = [101, 102, 103];\n\nasync function getPrice(id) {\n  // Simulação de requisição ao BD\n  return new Promise(resolve => {\n    setTimeout(() => {\n      resolve(`Preço para ${id}: $${Math.floor(Math.random() * 100)}`);\n    }, 1000);\n  });\n}\n\nproductIds.forEach(async (id) => {\n  const price = await getPrice(id);\n  console.log(price);\n});\n\nconsole.log('Todos os preços carregados!'); // Será executado imediatamente, não após 3 segundos
\n\n

Resultado no console:

\n
Todos os preços carregados!\nPreço para 101: $42\nPreço para 102: $78\nPreço para 103: $15
\n\n

Concordo, não é o que esperávamos. A boa notícia: existem soluções simples e elegantes.

\n\n

Solução nº1: for...of — execução sequencial

\n\n

A maneira mais simples de fazer o async/await funcionar em um loop é usar for...of. Ao contrário do forEach, o for...of funciona com async/await nativamente, porque você controla o fluxo de execução dentro do bloco.

\n\n
const productIds = [101, 102, 103];\n\nasync function getPrice(id) {\n  return new Promise(resolve => {\n    setTimeout(() => {\n      resolve(`Preço para ${id}: $${Math.floor(Math.random() * 100)}`);\n    }, 1000);\n  });\n}\n\nasync function loadAllPrices() {\n  for (const id of productIds) {\n    const price = await getPrice(id);\n    console.log(price);\n  }\n  console.log('Todos os preços carregados!');\n}\n\nloadAllPrices();
\n\n

Agora o resultado será:

\n
Preço para 101: $42\n(após 1 segundo)\nPreço para 102: $78\n(após 1 segundo)\nPreço para 103: $15\n(após 1 segundo)\nTodos os preços carregados!
\n\n

Vantagens: simplicidade, legibilidade, ordem previsível.

\n

Desvantagens: lento — cada requisição espera a conclusão da anterior.

Blogs

Book Recommendations