<h2>AbortController: Dominando o Cancelamento de Operações Assíncronas</h2>
<p>O <code>AbortController</code> é uma API moderna do JavaScript que permite cancelar operações assíncronas como requisições fetch, timers e streams. Antes dessa API, não havia um padrão consistente para interromper operações em andamento, levando a vazamento de memória e comportamentos inesperados. Entender como implementá-lo corretamente é essencial para construir aplicações robustas e eficientes.</p>
<h3>Por que precisamos cancelar operações?</h3>
<p>Considere um cenário real: um usuário digita em um campo de busca, mas muda de ideia antes de clicar em buscar. Se a requisição foi feita e ainda está em trânsito, você quer cancelá-la para evitar processamento desnecessário, atualizar a UI com dados obsoletos ou desperdiçar banda de rede. O <code>AbortController</code> resolve exatamente esse problema.</p>
<h2>Conceitos Fundamentais</h2>
<h3>O que é AbortController?</h3>
<p><code>AbortController</code> é um objeto que fornece dois componentes: o controlador (<code>AbortController</code>) e o sinal (<code>AbortSignal</code>). O controlador permite enviar um comando de cancelamento, enquanto o sinal é passado para a operação assíncrona que será monitorada.</p>
<pre><code class="language-javascript">const controller = new AbortController();
const signal = controller.signal;
// signal é passado para operações que o suportam
// controller.abort() cancela tudo que está escutando esse signal</code></pre>
<h3>Anatomia básica</h3>
<pre><code class="language-javascript">const controller = new AbortController();
// Monitorar se a operação foi abortada
controller.signal.addEventListener('abort', () => {
console.log('Operação cancelada!');
});
// Cancelar após 3 segundos
setTimeout(() => controller.abort(), 3000);
// Verificar se já foi abortado
console.log(controller.signal.aborted); // true/false</code></pre>
<h2>Usando AbortController com Fetch</h2>
<h3>Cancelamento manual de requisições</h3>
<p>O caso mais comum é cancelar requisições HTTP. Veja como implementar corretamente:</p>
<pre><code class="language-javascript">const controller = new AbortController();
// Começar requisição
fetch('https://api.example.com/dados', {
signal: controller.signal
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Requisição foi cancelada');
} else {
console.error('Erro na requisição:', error);
}
});
// Cancelar após 5 segundos
setTimeout(() => controller.abort(), 5000);</code></pre>
<h3>Timeout automático com AbortSignal.timeout()</h3>
<p>A forma mais elegante é usar <code>AbortSignal.timeout()</code>, que cancela automaticamente após um tempo específico:</p>
<pre><code class="language-javascript">// Cancela automaticamente após 5 segundos
fetch('https://api.example.com/dados', {
signal: AbortSignal.timeout(5000)
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Requisição expirou');
}
});</code></pre>
<h2>Padrões Avançados</h2>
<h3>Gerenciar múltiplas operações com um único sinal</h3>
<p>Você pode reutilizar o mesmo sinal para cancelar várias operações em conjunto:</p>
<pre><code class="language-javascript">const controller = new AbortController();
Promise.all([
fetch('/api/usuarios', { signal: controller.signal }),
fetch('/api/posts', { signal: controller.signal }),
fetch('/api/comentarios', { signal: controller.signal })
])
.then(responses => Promise.all(responses.map(r => r.json())))
.then(data => console.log('Todos os dados carregados:', data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Uma ou mais requisições foram canceladas');
}
});
// Um cancel afeta todas as requisições
setTimeout(() => controller.abort(), 3000);</code></pre>
<h3>Combinar sinais com AbortSignal.any()</h3>
<p>Para cenários onde você quer que a primeira operação que terminar vença, use <code>AbortSignal.any()</code>:</p>
<pre><code class="language-javascript">const userController = new AbortController();
const timeoutSignal = AbortSignal.timeout(10000);
// O que terminar primeiro (usuário cancela ou timeout de 10s)
const combinedSignal = AbortSignal.any([
userController.signal,
timeoutSignal
]);
fetch('/api/dados-pesados', { signal: combinedSignal })
.then(r => r.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Operação cancelada ou expirou');
}
});
// Usuário pode cancelar manualmente
document.getElementById('cancelBtn').addEventListener('click', () => {
userController.abort();
});</code></pre>
<h3>Implementação em uma classe real</h3>
<pre><code class="language-javascript">class SearchAPI {
constructor() {
this.controller = null;
}
async buscar(termo) {
// Cancelar busca anterior se houver
if (this.controller) {
this.controller.abort();
}
this.controller = new AbortController();
try {
const response = await fetch(
https://api.example.com/search?q=${termo},
{ signal: this.controller.signal }
);
const data = await response.json();
return data;
} catch (error) {
if (error.name === 'AbortError') {
console.log('Busca cancelada');
} else {
throw error;
}
}
}
cancelar() {
if (this.controller) {
this.controller.abort();
}
}
}
// Uso
const search = new SearchAPI();
search.buscar('javascript');
setTimeout(() => search.cancelar(), 2000);</code></pre>
<h2>Conclusão</h2>
<p><strong>Três aprendizados principais:</strong></p>
<ol>
<li><strong>AbortController resolve um problema real</strong>: evita requisições fantasma, vazamento de memória e comportamentos inesperados em aplicações modernas. Use-o sempre que trabalhar com operações assíncronas que podem ser interrompidas.</li>
</ol>
<ol>
<li><strong>Use <code>AbortSignal.timeout()</code> para timeouts simples</strong>: não precisa de lógica complexa com <code>setTimeout</code>. A API oferece exatamente o que você precisa de forma limpa.</li>
</ol>
<ol>
<li><strong>Combine sinais para cenários complexos</strong>: <code>AbortSignal.any()</code> permite orquestrar múltiplas operações com elegância, cobrindo casos onde timeout do usuário e timeout automático precisam coexistir.</li>
</ol>
<p>A prática consistente com essas técnicas transformará suas aplicações em código mais profissional e confiável.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController" target="_blank" rel="noopener noreferrer">MDN Web Docs - AbortController</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal" target="_blank" rel="noopener noreferrer">MDN Web Docs - AbortSignal</a></li>
<li><a href="https://github.com/tc39/proposal-promise-with-resolvers" target="_blank" rel="noopener noreferrer">TC39 - Promise.withResolvers Proposal</a></li>
<li><a href="https://javascript.info/fetch" target="_blank" rel="noopener noreferrer">JavaScript.info - Fetch API</a></li>
<li><a href="https://web.dev/abort-signals/" target="_blank" rel="noopener noreferrer">Web.dev - Abort signals in JavaScript</a></li>
</ul>