Async/await: escrita assíncrona síncrona
1. Fundamentos do Async/await
O JavaScript é single-threaded, mas gerencia operações assíncronas através do event loop. Antes do async/await, lidávamos com callbacks e Promises — ambos funcionais, mas propensos a aninhamentos complexos ou encadeamentos verbosos.
O async transforma qualquer função em uma função que retorna uma Promise implicitamente. O await pausa a execução da função assíncrona até que a Promise seja resolvida, sem bloquear a thread principal. Isso permite escrever código assíncrono com a mesma linearidade do código síncrono.
// Função síncrona
function somar(a, b) {
return a + b;
}
// Função assíncrona equivalente
async function somarAsync(a, b) {
return a + b; // automaticamente wrapped em Promise.resolve()
}
somarAsync(2, 3).then(console.log); // 5
A diferença crucial: enquanto o código síncrono bloqueia a execução até obter o resultado, o async/await libera a thread para outras tarefas durante a espera.
2. Sintaxe e Declaração de Funções Assíncronas
Podemos declarar funções assíncronas de várias formas:
// Declaração tradicional
async function buscarUsuario(id) {
const resposta = await fetch(`https://api.exemplo.com/usuarios/${id}`);
return resposta.json();
}
// Arrow function assíncrona
const buscarUsuario = async (id) => {
const resposta = await fetch(`https://api.exemplo.com/usuarios/${id}`);
return resposta.json();
};
// Método em objeto/class
const api = {
async getUsuario(id) {
return await fetch(`/usuarios/${id}`).then(res => res.json());
}
};
O await só pode ser usado dentro de funções async. Ele espera a resolução de qualquer Promise — seja de fetch, setTimeout convertido em Promise, ou APIs personalizadas.
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function exemploDelay() {
console.log('Início');
await delay(2000);
console.log('Após 2 segundos');
}
3. Tratamento de Erros com try/catch
Erros em funções assíncronas são capturados com try/catch, similar ao código síncrono:
async function buscarDados() {
try {
const resposta = await fetch('https://api.exemplo.com/dados');
if (!resposta.ok) throw new Error(`HTTP ${resposta.status}`);
const dados = await resposta.json();
return dados;
} catch (erro) {
console.error('Falha na requisição:', erro.message);
throw erro; // re-lança para quem chamou tratar
} finally {
console.log('Operação finalizada');
}
}
Erros não capturados em funções async propagam como Promises rejeitadas. É essencial ter um catch global ou usar process.on('unhandledRejection') no Node.js.
4. Async/await vs. Promises Tradicionais
Comparação direta entre encadeamento .then() e async/await:
// Com Promises encadeadas
fetch('/usuario/1')
.then(res => res.json())
.then(usuario => fetch(`/posts?userId=${usuario.id}`))
.then(res => res.json())
.then(posts => console.log(posts))
.catch(err => console.error(err));
// Com async/await
async function carregarPostsDoUsuario() {
try {
const resUsuario = await fetch('/usuario/1');
const usuario = await resUsuario.json();
const resPosts = await fetch(`/posts?userId=${usuario.id}`);
const posts = await resPosts.json();
console.log(posts);
} catch (err) {
console.error(err);
}
}
Async/await oferece legibilidade superior para fluxos sequenciais. No entanto, Promises ainda são úteis para paralelismo com Promise.all.
5. Execução Paralela com Async/await
Para executar múltiplas operações simultaneamente, combinamos await com Promise.all:
async function carregarDashboard() {
try {
const [usuarios, produtos, pedidos] = await Promise.all([
fetch('/api/usuarios').then(r => r.json()),
fetch('/api/produtos').then(r => r.json()),
fetch('/api/pedidos').then(r => r.json())
]);
return { usuarios, produtos, pedidos };
} catch (erro) {
console.error('Falha ao carregar dashboard:', erro);
}
}
Cuidado com loops: forEach não respeita await corretamente. Prefira for...of ou Promise.all com map:
// ERRADO - forEach não espera
async function processarItensErrado(itens) {
itens.forEach(async (item) => {
await processar(item); // executa concorrentemente, não em série
});
}
// CORRETO - for...of para série
async function processarItensSerie(itens) {
for (const item of itens) {
await processar(item);
}
}
// CORRETO - Promise.all para paralelo
async function processarItensParalelo(itens) {
await Promise.all(itens.map(item => processar(item)));
}
6. Async/await no Node.js
No Node.js, operações de I/O se beneficiam enormemente do async/await:
import { readFile, writeFile } from 'fs/promises';
async function processarArquivo() {
try {
const dados = await readFile('./entrada.txt', 'utf-8');
const processado = dados.toUpperCase();
await writeFile('./saida.txt', processado);
console.log('Arquivo processado com sucesso');
} catch (erro) {
console.error('Erro no processamento:', erro);
}
}
Em middlewares Express, funções assíncronas precisam de tratamento explícito de erros:
import express from 'express';
const app = express();
// Wrapper para capturar erros assíncronos
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/usuario/:id', asyncHandler(async (req, res) => {
const usuario = await buscarUsuario(req.params.id);
res.json(usuario);
}));
// Ou usar express-async-errors
import 'express-async-errors';
app.get('/usuario/:id', async (req, res) => {
const usuario = await buscarUsuario(req.params.id);
res.json(usuario);
});
7. Async/await no React
No React, useEffect não aceita diretamente funções async. Usamos uma função interna ou IIFE:
import { useState, useEffect } from 'react';
function ListaUsuarios() {
const [usuarios, setUsuarios] = useState([]);
const [loading, setLoading] = useState(true);
const [erro, setErro] = useState(null);
useEffect(() => {
const controller = new AbortController();
const carregarUsuarios = async () => {
try {
setLoading(true);
const resposta = await fetch('/api/usuarios', {
signal: controller.signal
});
if (!resposta.ok) throw new Error('Erro na requisição');
const dados = await resposta.json();
setUsuarios(dados);
} catch (erro) {
if (erro.name !== 'AbortError') {
setErro(erro.message);
}
} finally {
setLoading(false);
}
};
carregarUsuarios();
// Cleanup para evitar memory leaks
return () => controller.abort();
}, []);
if (loading) return <div>Carregando...</div>;
if (erro) return <div>Erro: {erro}</div>;
return (
<ul>
{usuarios.map(usuario => (
<li key={usuario.id}>{usuario.nome}</li>
))}
</ul>
);
}
O AbortController previne atualizações de estado em componentes desmontados, evitando memory leaks.
8. Boas Práticas e Armadilhas Comuns
Armadilhas frequentes:
// 1. await desnecessário
async function exemplo() {
const promise = fetch('/api'); // já é Promise
const resultado = await promise; // OK, mas poderia ser direto
}
// 2. Misturar callbacks com async
async function lerArquivoCallback() {
// ERRADO: callback não espera await
fs.readFile('arquivo.txt', async (err, data) => {
await processar(data); // pode causar erros não capturados
});
}
// 3. Erro não tratado em Promise.all
async function exemploParalelo() {
const resultados = await Promise.all([
operacaoQuePodeFalhar(),
outraOperacao()
]).catch(err => {
// Se uma falhar, todas falham
console.error('Uma das operações falhou');
});
}
Boas práticas:
- Sempre trate erros com
try/catchem funções async - Use
Promise.allSettledquando precisar de resultados parciais - Evite
awaitdentro de loops quando as operações são independentes - Prefira
for...ofpara operações sequenciais - Utilize
AbortControllerno React para cancelar requisições
Async/await revolucionou a forma como escrevemos código assíncrono em JavaScript, tornando-o mais legível e manutenível. Dominar seus padrões e armadilhas é essencial para qualquer desenvolvedor moderno.
Referências
- MDN Web Docs: async function — Documentação oficial sobre a declaração de funções assíncronas e uso do await.
- Node.js Documentation: fs/promises — Guia oficial da API de promises do módulo fs no Node.js.
- React Documentation: useEffect — Documentação oficial sobre efeitos colaterais e boas práticas com funções assíncronas no React.
- JavaScript.info: Async/await — Tutorial completo e detalhado sobre async/await com exemplos práticos.
- Express.js Error Handling — Guia oficial sobre tratamento de erros, incluindo middlewares assíncronos no Express.
- Using AbortController in React — Artigo técnico sobre como evitar memory leaks com AbortController em componentes React.