Pular para o conteúdo principal

AbortController: A API mais subestimada do JavaScript

· Leitura de 4 minutos
Thiago Sciotta
Principal Engineer

Todo mundo conhece o AbortController pra cancelar fetch(). Beleza, caso de uso clássico. Mas se isso é tudo que você sabe, tá usando 10% do que ele oferece.

O AbortController é um primitivo universal de cancelamento. Ele cancela fetch, event listeners, streams, operações de arquivo no Node.js, e qualquer coisa async que aceite um signal. E o padrão é sempre o mesmo: criar, passar, cancelar.

O básico que todo mundo conhece

const controller = new AbortController();

fetch('/api/data', { signal: controller.signal });

// Precisa cancelar? Uma linha:
controller.abort();

Três passos. Sempre os mesmos. Mas o que pouca gente sabe é que esse mesmo padrão funciona em muito mais lugares.

1. Event listeners com auto-remoção

Já escreveu isso?

el.addEventListener('click', handler);
el.addEventListener('keydown', handler);
el.addEventListener('scroll', handler);

// Depois, lá embaixo no código...
el.removeEventListener('click', handler);
el.removeEventListener('keydown', handler);
el.removeEventListener('scroll', handler);

Verbose demais. E fácil de esquecer. Com AbortController:

const ctrl = new AbortController();

el.addEventListener('click', handler, { signal: ctrl.signal });
el.addEventListener('keydown', handler, { signal: ctrl.signal });
el.addEventListener('scroll', handler, { signal: ctrl.signal });

// Um abort() remove TODOS de uma vez
ctrl.abort();

Um abort() limpa tudo. Sem precisar guardar referência de cada listener. Essa feature existe desde 2021 e a maioria dos devs não sabe.

2. React useEffect sem memory leak

Esse aqui é clássico. Quantas vezes você já viu (ou escreveu) isso?

// ❌ Memory leak: seta state em componente desmontado
useEffect(() => {
fetch('/api/users')
.then(r => r.json())
.then(setUsers);
}, []);

O componente desmonta, o fetch termina, e o React reclama que você tá tentando atualizar state de um componente que não existe mais. A fix:

// ✅ Cleanup correto: cancela no unmount
useEffect(() => {
const ctrl = new AbortController();

fetch('/api/users', { signal: ctrl.signal })
.then(r => r.json())
.then(setUsers)
.catch(e => {
if (e.name !== 'AbortError') throw e;
});

return () => ctrl.abort();
}, []);

O return () => ctrl.abort() é chamado automaticamente quando o componente desmonta. O fetch é cancelado. Sem memory leak. Sem warning no console.

3. Timeout nativo com AbortSignal.timeout()

Antes, pra colocar timeout num fetch, você precisava de um setTimeout + AbortController manual. Agora:

// Cancela automaticamente depois de 5 segundos
const res = await fetch('/api/slow-endpoint', {
signal: AbortSignal.timeout(5000)
});

Uma linha. Sem setTimeout. Sem cleanup. Built into the platform.

4. Combinar múltiplos sinais com AbortSignal.any()

E se você precisa cancelar uma requisição se o usuário clicar em cancelar OU se passar 5 segundos?

const ctrl = new AbortController();
const timeoutSignal = AbortSignal.timeout(5000);

const combined = AbortSignal.any([
ctrl.signal, // cancelamento manual
timeoutSignal // timeout de 5s
]);

// Aborta se: usuário cancela OU timeout
await fetch('/api/data', { signal: combined });

AbortSignal.any() combina múltiplas condições de cancelamento. Suportado em todos os browsers modernos desde 2024.

5. Cancelar streams

Download grande que o usuário quer cancelar no meio?

const ctrl = new AbortController();

const response = await fetch('/api/large-file', {
signal: ctrl.signal
});

const reader = response.body.getReader();

// Usuário clica em "Cancelar download"
ctrl.abort(); // Stream para imediatamente

O stream é interrompido na hora. Sem esperar o download terminar. Sem desperdiçar banda.

6. Operações de arquivo no Node.js

No backend também funciona:

import { readFile } from 'fs/promises';

const ctrl = new AbortController();

// Cancela se demorar mais de 1 segundo
setTimeout(() => ctrl.abort(), 1000);

const data = await readFile('huge-file.txt', {
signal: ctrl.signal
});

O padrão universal

Não importa o que você tá cancelando. O padrão é sempre o mesmo:

1. Criar:   const ctrl = new AbortController()
2. Passar: { signal: ctrl.signal }
3. Cancelar: ctrl.abort()

Um controller. Múltiplas operações. Um abort() cancela tudo.

Quando usar

CenárioSem AbortControllerCom AbortController
Cleanup de listenersremoveEventListener() pra cada umUm abort() remove todos
React unmountMemory leak ou flag isMountedreturn () => ctrl.abort()
Timeout em fetchsetTimeout + controller manualAbortSignal.timeout(ms)
Cancelar múltiplas condiçõesLógica custom complexaAbortSignal.any([...])
Download cancelávelFlag manual + check no loopctrl.abort() no botão

Quando NÃO usar

  • Operações síncronas — AbortController é pra async. Se é síncrono, não precisa.
  • Fire and forget — Se você não precisa cancelar, não complique. Nem toda requisição precisa de um controller.
  • Quando o cleanup não importa — Se o componente não vai desmontar, se a página vai recarregar, não adicione complexidade desnecessária.

Como sempre: use quando resolve um problema real, não pra mostrar que sabe.


O AbortController é provavelmente a API mais subestimada do JavaScript. Todo mundo conhece o caso de uso básico, mas quase ninguém explora o potencial completo. É simples, elegante, e já tá no seu browser. Só falta usar.