<h2>Entendendo Promises e seu Tratamento de Erros</h2>
<p>Uma Promise é a base do tratamento de erros assíncronos em JavaScript. Ela representa uma operação que pode estar pendente, resolvida ou rejeitada. Quando uma Promise é rejeitada, ela dispara um erro que precisa ser capturado. O método <code>.catch()</code> é a forma clássica de tratar esses erros, funcionando como um <code>try-catch</code> para operações assíncronos.</p>
<pre><code class="language-javascript">function buscarDados(url) {
return fetch(url)
.then(response => response.json())
.catch(erro => {
console.error('Erro ao buscar dados:', erro.message);
return { dados: [] };
});
}
buscarDados('https://api.example.com/users')
.then(resultado => console.log(resultado))
.catch(erro => console.error('Erro final:', erro));</code></pre>
<p>Neste exemplo, o primeiro <code>.catch()</code> captura erros da requisição ou parsing JSON. Se houver um erro não tratado, o segundo <code>.catch()</code> será acionado. É importante entender que cada <code>.catch()</code> ou <code>.then()</code> cria uma nova Promise, permitindo encadeamento seguro.</p>
<h2>Async/Await com Try-Catch</h2>
<p>O async/await é a sintaxe moderna e mais legível para trabalhar com Promises. Com ele, código assíncronos parece síncrono, e podemos usar <code>try-catch</code> tradicionais para tratar erros. Esta é a abordagem recomendada para novos projetos.</p>
<pre><code class="language-javascript">async function buscarUsuario(id) {
try {
const response = await fetch(https://api.example.com/users/${id});
if (!response.ok) {
throw new Error(HTTP ${response.status}: ${response.statusText});
}
const usuario = await response.json();
return usuario;
} catch (erro) {
console.error('Falha ao buscar usuário:', erro.message);
throw erro;
} finally {
console.log('Requisição finalizada');
}
}
// Uso
buscarUsuario(1)
.then(user => console.log('Usuário:', user))
.catch(erro => console.error('Não foi possível obter usuário'));</code></pre>
<p>Observe que o bloco <code>finally</code> executa sempre, independente de sucesso ou erro. Isso é útil para limpeza de recursos. Quando você relança um erro com <code>throw</code>, ele propaga para a Promise retornada, permitindo tratamento em nível superior.</p>
<h3>Tratando Múltiplas Operações Assíncronas</h3>
<p>Quando você precisa executar várias operações em paralelo, <code>Promise.all()</code> é eficiente, mas rejeita se qualquer Promise falhar. <code>Promise.allSettled()</code> é mais seguro, pois aguarda todas as Promises e reporta o resultado de cada uma.</p>
<pre><code class="language-javascript">async function carregarDados() {
try {
const [usuarios, posts, comentarios] = await Promise.all([
fetch('https://api.example.com/users').then(r => r.json()),
fetch('https://api.example.com/posts').then(r => r.json()),
fetch('https://api.example.com/comments').then(r => r.json())
]);
return { usuarios, posts, comentarios };
} catch (erro) {
console.error('Uma ou mais requisições falharam:', erro);
return null;
}
}
// Versão com allSettled para mais controle
async function carregarDadosSeguro() {
const resultados = await Promise.allSettled([
fetch('https://api.example.com/users').then(r => r.json()),
fetch('https://api.example.com/posts').then(r => r.json()),
fetch('https://api.example.com/comments').then(r => r.json())
]);
resultados.forEach((resultado, idx) => {
if (resultado.status === 'rejected') {
console.error(Requisição ${idx} falhou:, resultado.reason);
}
});
return resultados;
}</code></pre>
<h2>Tratamento Avançado e Boas Práticas</h2>
<h3>Timeout e Retry com Exponential Backoff</h3>
<p>Em sistemas reais, você precisa lidar com timeouts e implementar retry com backoff exponencial para recuperação automática de falhas temporárias.</p>
<pre><code class="language-javascript">async function fetchComTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeoutId);
return await response.json();
} catch (erro) {
clearTimeout(timeoutId);
if (erro.name === 'AbortError') {
throw new Error('Requisição expirou');
}
throw erro;
}
}
async function fetchComRetry(url, maxTentativas = 3, delayInicial = 1000) {
let ultimoErro;
for (let tentativa = 0; tentativa < maxTentativas; tentativa++) {
try {
return await fetchComTimeout(url);
} catch (erro) {
ultimoErro = erro;
if (tentativa < maxTentativas - 1) {
const delay = delayInicial * Math.pow(2, tentativa);
console.log(Tentativa ${tentativa + 1} falhou. Aguardando ${delay}ms...);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw ultimoErro;
}
// Uso
fetchComRetry('https://api.example.com/dados')
.then(dados => console.log('Sucesso:', dados))
.catch(erro => console.error('Todas as tentativas falharam:', erro.message));</code></pre>
<h3>Tratamento Centralizado de Erros</h3>
<p>Em aplicações maiores, é bom centralizar a lógica de tratamento de erros usando middleware ou wrapper functions.</p>
<pre><code class="language-javascript">class GerenciadorErros {
static async executar(funcao, contexto = '') {
try {
return await funcao();
} catch (erro) {
this.registrar(erro, contexto);
this.notificarUsuario(erro);
throw erro;
}
}
static registrar(erro, contexto) {
console.error([${contexto}] ${erro.message}, erro.stack);
// Enviar para serviço de logging
}
static notificarUsuario(erro) {
// Mostrar mensagem amigável ao usuário
const mensagem = erro.message.includes('timeout')
? 'A operação demorou muito. Tente novamente.'
: 'Ocorreu um erro inesperado. Tente mais tarde.';
console.warn('Notificação:', mensagem);
}
}
// Uso
GerenciadorErros.executar(
() => fetchComRetry('https://api.example.com/dados'),
'Carregamento de Dados'
)
.then(dados => console.log(dados))
.catch(() => {/ Já tratado /});</code></pre>
<h2>Conclusão</h2>
<p>O tratamento de erros assíncronos em JavaScript é fundamental para aplicações robustas. Três pontos-chave para lembrar: <strong>Primeiro</strong>, prefira async/await com try-catch para código mais legível e manutenível. <strong>Segundo</strong>, sempre implemente timeout e retry com backoff exponencial em requisições de rede, pois falhas temporárias são comuns. <strong>Terceiro</strong>, centralize sua lógica de tratamento de erros para evitar duplicação e facilitar manutenção — seus colegas e seu eu do futuro agradecerão.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" target="_blank" rel="noopener noreferrer">MDN Web Docs - Promise</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Promises" target="_blank" rel="noopener noreferrer">MDN Web Docs - Async/Await</a></li>
<li><a href="https://javascript.info/try-catch" target="_blank" rel="noopener noreferrer">JavaScript.info - Error handling, "try...catch"</a></li>
<li><a href="https://nodejs.org/en/docs/guides/error-handling/" target="_blank" rel="noopener noreferrer">Node.js Official Documentation - Error Handling</a></li>
<li><a href="https://github.com/getify/You-Dont-Know-JS" target="_blank" rel="noopener noreferrer">You Don't Know JS Yet - Async & Performance</a></li>
</ul>