AbortController é a primitiva de cancelamento do browser: cria um signal que pode ser passado para fetch, Timers, Event Listeners e qualquer API que o suporte. Quando controller.abort() é chamado, todos os receptores do signal são notificados e a operação é cancelada. É a solução correta para: cancelar requests desatualizados ao mudar de rota, implementar timeout de rede, ou limpar recursos quando um componente é desmontado.
AbortController básico
const controller = new AbortController();const signal = controller.signal;signal.aborted; // false inicialmentesignal.reason; // undefined, ou a razão passada para abort()// Cancelarcontroller.abort('Usuário navegou para outra página'); // razão opcionalsignal.aborted; // truesignal.reason; // 'Usuário navegou para outra página'
Cancelar um fetch
const controller = new AbortController();// Passar o signal para o fetchconst fetchPromise = fetch('/api/data', { signal: controller.signal,});// Cancelar após 5 segundos (timeout manual)const timeoutId = setTimeout(() => { controller.abort('Timeout: requisição demorou mais de 5s');}, 5000);try { const response = await fetchPromise; clearTimeout(timeoutId); // limpar timeout se completou antes const data = await response.json(); return data;} catch (error) { if (error.name === 'AbortError') { console.log('Request cancelado:', error.message); return null; } throw error; // re-throw erros de rede reais}
AbortSignal.timeout() — a forma mais simples
// Criar um signal que aborta automaticamente após N msconst response = await fetch('/api/data', { signal: AbortSignal.timeout(5000), // timeout de 5 segundos});
Combinando timeouts
Use AbortSignal.any([signal1, signal2]) para combinar múltiplos signals — aborta quando qualquer um deles for acionado:
const response = await fetch(url, { signal: AbortSignal.any([ userCancelController.signal, // cancelamento do usuário AbortSignal.timeout(10000), // timeout de 10s ]),});
Padrão: cancelar requisição anterior ao pesquisar
let currentController = null;async function searchProducts(query) { // Cancelar request anterior se ainda estiver em andamento currentController?.abort('Nova pesquisa iniciada'); currentController = new AbortController(); try { const response = await fetch(`/api/products?q=${encodeURIComponent(query)}`, { signal: currentController.signal, }); if (!response.ok) throw new Error(`HTTP ${response.status}`); return await response.json(); } catch (error) { if (error.name === 'AbortError') return; // ignorar cancelamentos throw error; }}// Input de busca: cancela request anterior ao digitarsearchInput.addEventListener('input', () => { searchProducts(searchInput.value);});
Padrão: cancelar ao sair do componente (React)
// React: cancelar fetch ao desmontar o componentefunction useProductSearch(query) { const [products, setProducts] = useState([]); useEffect(() => { const controller = new AbortController(); async function load() { try { const response = await fetch(`/api/products?q=${query}`, { signal: controller.signal, }); const data = await response.json(); setProducts(data); } catch (error) { if (error.name !== 'AbortError') { console.error(error); } } } load(); // Cleanup: cancelar quando componente desmontar ou query mudar return () => controller.abort(); }, [query]); return products;}
Cancelar Event Listeners com AbortSignal
const controller = new AbortController();const { signal } = controller;// Adicionar múltiplos listeners que todos serão removidos de uma vezdocument.addEventListener('click', handleClick, { signal });document.addEventListener('keydown', handleKeydown, { signal });window.addEventListener('resize', handleResize, { signal });// Remover todos de uma vezcontroller.abort();// Equivalente a chamar removeEventListener para cada um — mas muito mais limpo
Propagar cancelamento para operações customizadas
// Verificar se foi cancelado dentro de uma operação longaasync function processLargeFile(file, signal) { const chunks = splitIntoChunks(file, 1024); for (const chunk of chunks) { // Verificar cancelamento antes de cada chunk if (signal.aborted) { throw new DOMException('Processamento cancelado', 'AbortError'); } await processChunk(chunk); }}// Ouvir o evento 'abort' para cancelar operações assíncronasasync function streamingOperation(signal) { return new Promise((resolve, reject) => { const abortHandler = () => { // Limpar recursos e rejeitar cleanup(); reject(new DOMException('Operação cancelada', 'AbortError')); }; signal.addEventListener('abort', abortHandler, { once: true }); startOperation((result) => { signal.removeEventListener('abort', abortHandler); resolve(result); }); });}
Identificar AbortError corretamente
function isAbortError(error) { // Verificar pela propriedade name (não instanceof — DOMException é complicado) return error.name === 'AbortError';}try { await fetch(url, { signal });} catch (error) { if (isAbortError(error)) { // Cancelamento esperado — não logar como erro return; } // Erro de rede real — logar e tratar logError(error); throw error;}
Para fixar
O que controller.abort() faz? Como o fetch recebe a notificação de cancelamento?
Qual a diferença entre AbortSignal.timeout(5000) e usar setTimeout(() => controller.abort(), 5000)?
Como você cancelaria múltiplos listeners de evento de uma vez?
Por que um fetch cancelado lança AbortError em vez de simplesmente resolver a Promise?
Como você propagaria cancelamento para uma operação customizada (não-fetch)?