Async/await sem pegadinha: promises, AbortController e cancelamento
⏱ 12 min·⭐ 50 XP
Pré-requisitos (0/1)0%
- ⬜🛡️ Type safety em boundaries: Zod, io-ts e validação runtime(TypeScript Profissional)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
Promise é um valor, não uma função
A maior confusão: devs tratam Promise como "callback melhorado". Na verdade, é um valor que representa um resultado futuro. Esse valor pode estar em 3 estados: pending, fulfilled, rejected.
// Promise é um valor
const p: Promise<number> = fetchData();
// p existe agora mesmo; o número virá depois
// async wrappa função em Promise automaticamente
async function fetchData(): Promise<number> {
const res = await fetch('/api'); // await "desempacota" a Promise
return res.json();
}Paralelismo: Promise.all vs sequência
| Combinador | Termina quando | Uma falha causa | Caso de uso |
|---|---|---|---|
| Todas | Todas resolvem | Rejeição imediata — resultados prontos são descartados | Chamadas independentes em que todas são necessárias |
| Todas com resultado | Todas terminam, com sucesso ou não | Nada — cada uma reporta o próprio estado | Quando o parcial serve: painel com quatro fontes, uma fora do ar |
| A primeira que resolver | A primeira a ter sucesso | Só falha se TODAS falharem | Redundância: dois espelhos, o que responder primeiro |
| A primeira que assentar | A primeira a resolver OU rejeitar | A rejeição vence a corrida | Aplicar tempo limite competindo com a operação |
CombinadorTodas
Termina quandoTodas resolvem
Uma falha causaRejeição imediata — resultados prontos são descartados
Caso de usoChamadas independentes em que todas são necessárias
CombinadorTodas com resultado
Termina quandoTodas terminam, com sucesso ou não
Uma falha causaNada — cada uma reporta o próprio estado
Caso de usoQuando o parcial serve: painel com quatro fontes, uma fora do ar
CombinadorA primeira que resolver
Termina quandoA primeira a ter sucesso
Uma falha causaSó falha se TODAS falharem
Caso de usoRedundância: dois espelhos, o que responder primeiro
CombinadorA primeira que assentar
Termina quandoA primeira a resolver OU rejeitar
Uma falha causaA rejeição vence a corrida
Caso de usoAplicar tempo limite competindo com a operação
// ❌ Sequencial (300ms + 300ms = 600ms total)
const a = await fetch('/api/a');
const b = await fetch('/api/b');
// ✅ Paralelo (300ms total)
const [a, b] = await Promise.all([
fetch('/api/a'),
fetch('/api/b'),
]);
// Quando pode falhar e você quer saber qual:
const results = await Promise.allSettled([
fetch('/api/slow'),
fetch('/api/maybe-down'),
]);
// results: [{status: 'fulfilled', value}, {status: 'rejected', reason}]💡
Regra prática: se os awaits não dependem um do outro, use Promise.all. Dev sênior faz isso por instinto; júnior sofre latência desnecessária.
📅 Três chamadas de 1s cada — sequencial contra Promise.all
0s
await fetch(A) — sequencial
Começa; nada mais roda até esta terminar
1s
await fetch(B) — sequencial
Só começa agora, porque esperou A terminar
2s
await fetch(C) — sequencial
Total sequencial: 3 segundos
0s
Promise.all([A, B, C])
As três disparam JUNTAS, no mesmo instante
1s
As três terminam juntas
Total paralelo: 1 segundo — o tempo da mais lenta, não a soma
AbortController: cancelamento de verdade
// Em um React effect:
useEffect(() => {
const controller = new AbortController();
fetch('/api/users', { signal: controller.signal })
.then(r => r.json())
.then(setUsers)
.catch(err => {
if (err.name === 'AbortError') return; // esperado
console.error(err);
});
return () => controller.abort(); // cleanup: cancela fetch
}, []);
// Timeout manual:
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch(url, { signal: controller.signal });
clearTimeout(id);
return res;
} catch (err) { /* ... */ }Quiz rápido
Dois `fetch` independentes, 300 ms cada, escritos com `await` em sequência. Qual é o custo, e qual a correção?
Armadilhas comuns
- forEach com async: NÃO espera. Use ou .
- Double await: é legal mas desnecessário (await já desempacota uma vez).
- Esquecer await: fora de async retorna Promise e continua sem esperar. TS avisa se você ligar .
- setTimeout async: o callback retorna Promise que ninguém aguarda. Erros viram unhandled.
Perguntas frequentes
❓ Promessa é uma função?
Não: é um valor que representa um resultado futuro, e ela já começou a executar quando você a tem em mãos. É por isso que criar promessas em laço e esperar depois roda tudo em paralelo — o que às vezes é o que se quer e às vezes derruba o serviço de destino.
❓ Como esperar várias operações corretamente?
Com a combinação que espera todas quando você precisa de todas, e com a variante que não interrompe no primeiro erro quando você quer o resultado parcial. A escolha errada é a que descarta resultado já obtido porque um item falhou.
❓ Para que serve o mecanismo de cancelamento?
Para abortar operação que o usuário não espera mais — navegação trocada, busca digitada de novo. Sem cancelamento, requisições antigas continuam e podem sobrescrever a resposta nova, que é o defeito clássico de busca conforme se digita.
Fixando
Quiz rápido
Por que `array.forEach(async item => { await salvar(item) })` não espera nada?
Quiz rápido
Num `useEffect` que faz `fetch`, para que serve o `AbortController`?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…