Usamos cookies para medir audiência e melhorar sua experiência. Você pode aceitar ou recusar a qualquer momento. Veja sobre o iMasters.
Quando se trata de manipulação assíncrona de listas em JavaScript, uma prática bastante comum, mas altamente problemática, é usar o método forEach com funções assíncronas. A ideia de disparar operações paralelas dentro de um forEach soa eficiente à primeira vista, especialmente para quem quer processar múltiplos arquivos ou requisições ao mesmo tempo. Contudo, essa abordagem tem várias armadilhas que podem comprometer a confiabilidade do seu código.
---
Muitos desenvolvedores, ao verem que o código 'funciona', acabam pensando que tudo está sob controle. Mas a verdade é que o forEach não lida com promessas retornadas de funções assíncronas da maneira que imaginamos. Ele simplesmente ignora o fato de que a callback assíncrona retorna uma promessa. Como consequência, a função principal pode terminar sua execução antes de todas as operações assíncronas serem concluídas. A decisão fica mais saudável quando o time consegue medir o impacto depois.
Essa situação pode gerar efeitos colaterais inesperados, como logs fora de ordem, operações incompletas ou até mesmo erros de concorrência em contextos mais sensíveis.
---
O método forEach foi concebido para execução síncrona. Quando você passa uma função assíncrona, ela é invocada, mas a promessa retornada não é aguardada pelo forEach. Assim, o fluxo de execução continua, e a função que chamou o forEach pode já ter retornado antes que as operações assíncronas terminem.
O uso de await dentro de uma callback de forEach não faz com que a próxima iteração espere a conclusão da anterior, nem garante a finalização de todas as tarefas antes do término da função envolvente.
Para leitura sequencial, a solução é usar um loop for...of, que permite o uso de await de forma natural e garantida. Esse contexto ajuda a separar ganho real de novidade difícil de sustentar. A decisão fica mais saudável quando o time consegue medir o impacto depois. Sem esse critério, a solução pode parecer simples no começo e cara no suporte. O valor aparece melhor quando operação, produto e engenharia olham para o mesmo risco. Por isso, o recorte precisa considerar manutenção, validação e caminho de volta. Esse contexto ajuda a separar ganho real de novidade difícil de sustentar.
async function printFiles() {
const files = await getFilePaths(). for (const file of files) {
const contents = await fs.readFile(file, 'utf8'). console.log(contents). }
}
Se o objetivo é processamento paralelo, o caminho é usar map() para transformar os arquivos em promessas e, depois, aguardar todas com Promise.all. Sem esse critério, a solução pode parecer simples no começo e cara no suporte. O valor aparece melhor quando operação, produto e engenharia olham para o mesmo risco. Por isso, o recorte precisa considerar manutenção, validação e caminho de volta. Esse contexto ajuda a separar ganho real de novidade difícil de sustentar. A decisão fica mais saudável quando o time consegue medir o impacto depois. Sem esse critério, a solução pode parecer simples no começo e cara no suporte. O valor aparece melhor quando operação, produto e engenharia olham para o mesmo risco.
async function printFiles() {
const files = await getFilePaths(). await Promise.all(files.map(async (file) => {
const contents = await fs.readFile(file, 'utf8'). console.log(contents). })). } O valor aparece melhor quando operação, produto e engenharia olham para o mesmo risco.
Entender o comportamento de cada método assíncrono é vital para evitar problemas de execução e bugs difíceis de rastrear. Em operações assíncronas, prefira for...of para sequências e Promise.all para paralelismo controlado. Por isso, o recorte precisa considerar manutenção, validação e caminho de volta. O valor aparece melhor quando operação, produto e engenharia olham para o mesmo risco. O valor aparece melhor quando operação, produto e engenharia olham para o mesmo risco. Por isso, o recorte precisa considerar manutenção, validação e caminho de volta. O valor aparece melhor quando operação, produto e engenharia olham para o mesmo risco. O valor aparece melhor quando operação, produto e engenharia olham para o mesmo risco.
Se você já enfrentou problemas com esse padrão, compartilhe sua experiência. Melhor ainda, teste seu código com logs de início e fim das operações para garantir que a execução está conforme esperado. Esse contexto ajuda a separar ganho real de novidade difícil de sustentar. A decisão fica mais saudável quando o time consegue medir o impacto depois. Por isso, o recorte precisa considerar manutenção, validação e caminho de volta. Por isso, o recorte precisa considerar manutenção, validação e caminho de volta. Por isso, o recorte precisa considerar manutenção, validação e caminho de volta. Por isso, o recorte precisa considerar manutenção, validação e caminho de volta.
Concordo totalmente.
Realmente, acho que muita ge net cai nesse erro por não entenderem bem como o forEach funciona com promessas. No meu time, sempre recomendamos usar for...of ou Promise.all para esses casos.
Exato, Gabriel. Pra quem trabalha com operações sensíveis a ordem, o for...of é essencial. Já passei por isso, e a confusão sobre o comportamento do forEach pode gerar bugs difíceis de detectar depois.
No meu projeto, usar Promise.all ajudou demais na velocidade de processamento, mas tem que cuidar pra não sobrecarregar o servidor.