Async/await e Array.map: quando melhores amigos se tornam inimigos
Você já escreveu algo como const results = await array.map(async (item) => {...}) e obteve um array de Promise em vez dos dados esperados? Se sim — você não está sozinho. Esta pergunta no Stack Overflow acumulou dezenas de milhares de visualizações, e o número de desenvolvedores que pisaram neste rake é contado aos milhões.
Neste artigo, vamos analisar os 5 erros mais perigosos ao usar async/await com Array.map em TypeScript. E, mais importante, mostrarei como evitá-los. Aperte os cintos — vai doer, mas será útil.
Erro nº1: Esquecer o Promise.all (o mais popular)
O bug mais comum é pensar que await dentro de map magicamente esperará todas as promises. Spoiler: não, não espera.
Problema
// ❌ Assim não funcionaconst users = [1, 2, 3, 4, 5];const userData = await users.map(async (id) => { const response = await fetch(`/api/users/${id}`); return response.json();});// userData — é Promise[], não UserData[]!console.log(userData); // [Promise, Promise, Promise, Promise, Promise]Por que isso acontece?
Array.map é síncrono. Ele não espera a conclusão dos callbacks assíncronos. Cada chamada da função async retorna uma Promise, e o map simplesmente as coleta em um array. O await antes de map espera apenas a conclusão do próprio map, mas não de seus callbacks.
Solução
// ✅ Correto: envolvemos com Promise.allconst users = [1, 2, 3, 4, 5];const userData = await Promise.all( users.map(async (id) => { const response = await fetch(`/api/users/${id}`); return response.json(); }));// userData — UserData[]! 🎉console.log(userData);Erro nº2: Todas as promises são executadas em paralelo (mas precisamos em sequência)
Promise.all executa todas as promises em paralelo. Isso é ótimo para desempenho, mas às vezes precisamos executar requisições estritamente uma após a outra. Por exemplo, ao trabalhar com uma API que limita o número de requisições, ou quando cada requisição seguinte depende da anterior.
Problema
// ❌ Todas as requisições vão simultaneamenteconst ids = [1, 2, 3, 4, 5];const results = await Promise.all( ids.map(async (id) => { // 5 requisições à API ao mesmo tempo — pode levar a rate limit return await api.processItem(id); }));Solução: execução sequencial
// ✅ Usamos for...of com awaitconst ids = [1, 2, 3, 4, 5];const results: ItemData[] = [];
for (const id of ids) { const result = await api.processItem(id); results.push(result);}// results — array com dados processados em ordemAlternativa — Array.reduce com acumulador de promise:
// ✅ Também funciona, mas menos legívelconst ids = [1, 2, 3, 4, 5];const results = await ids.reduce(async (accPromise, id) => { const acc = await accPromise; const result = await api.processItem(id); return [...acc, result];}, Promise.resolve([] as ItemData[]));Erro nº3: Tipagem incorreta no TypeScript
TypeScript pode não entender imediatamente o que você está fazendo com map assíncrono. Especialmente se você usar Promise.all sem especificar explicitamente o tipo.
Problema
// ❌ TypeScript reclama ou infere o tipo erradoconst ids = [1, 2, 3];const results = await Promise.all( ids.map(async (id) => { const data = await fetchData(id); return data.name; // TypeScript pode pensar que é Promise<string> }));// results tem tipo unknown ou Promise<string>[]Solução: anotação explícita de tipo
// ✅ Especificamos explicitamente o tipo do resultado