Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

AbortController no Node.js: Guia Prático

Atualizado em: 28 de julho de 2026

Botão vermelho representando AbortController no Node.js

Aplicações Node.js executam muitas operações assíncronas: chamadas HTTP, consultas a serviços, leitura de arquivos, timers, streams e tarefas internas. Sem uma estratégia de cancelamento, uma operação pode continuar consumindo rede, memória e processamento mesmo depois que o cliente fecha a conexão ou o resultado deixa de ser necessário. O AbortController no Node.js oferece um padrão consistente para interromper esse trabalho.

Com um controlador, você cria um AbortSignal e o entrega às APIs compatíveis. Quando o cancelamento acontece, essas operações recebem o sinal e encerram o trabalho de forma controlada. O mesmo mecanismo pode representar timeout, encerramento solicitado pelo usuário, desligamento da aplicação ou substituição de uma tarefa por outra mais recente.

Neste guia, você aprenderá a usar AbortController com fetch(), timers baseados em Promises, streams e funções próprias. Também verá como combinar sinais, registrar motivos, evitar listeners acumulados, testar cancelamentos e distinguir cancelamento de falhas reais.

O que é AbortController?

AbortController é uma classe global compatível com o padrão da Web. Cada instância possui uma propriedade signal, que representa o estado do cancelamento. Quando abort() é chamado, o sinal passa a indicar que a operação deve ser encerrada.

const controller = new AbortController();
const signal = controller.signal;

console.log(signal.aborted); // false

controller.abort();

console.log(signal.aborted); // true

O sinal não encerra automaticamente qualquer função JavaScript. A API que recebe o sinal precisa observar o estado e reagir. Diversas APIs nativas do Node.js já aceitam essa opção, e funções internas da aplicação também podem implementar o mesmo contrato.

A documentação oficial de AbortController no Node.js descreve AbortSignal.timeout(), AbortSignal.any(), reason e throwIfAborted().

Para revisar a base da plataforma, consulte o que é Node.js, o que é JavaScript e o que é uma API.

Controller e signal têm responsabilidades diferentes

O controlador é usado por quem possui autoridade para cancelar. O sinal é entregue ao código que executa a operação. Separar essas responsabilidades evita que uma função interna interrompa arbitrariamente um fluxo que pertence a outra camada.

async function loadReport(signal) {
  const response = await fetch('https://api.example.com/report', {
    signal
  });

  return response.json();
}

const controller = new AbortController();
const promise = loadReport(controller.signal);

controller.abort();

await promise;

A função loadReport() recebe apenas o sinal. A camada que iniciou a tarefa mantém o controlador e decide quando cancelar.

Cancelando uma requisição com fetch()

O uso mais conhecido é cancelar uma chamada HTTP:

const controller = new AbortController();

const request = fetch('https://api.example.com/products', {
  signal: controller.signal
});

setTimeout(() => {
  controller.abort();
}, 2000);

try {
  const response = await request;
  const data = await response.json();
  console.log(data);
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Requisição cancelada');
  } else {
    throw error;
  }
}

Quando o cancelamento é recebido, a Promise é rejeitada. O código deve tratar esse caso separadamente de DNS indisponível, conexão recusada, resposta inválida e outros erros operacionais.

Cancelar reduz trabalho desnecessário no cliente HTTP, mas não garante que o servidor remoto desfaça tudo que já executou. Uma requisição de pagamento, por exemplo, pode ter sido processada antes da conexão ser encerrada. Operações com efeitos permanentes precisam de idempotência e confirmação de estado.

Timeout simples com AbortSignal.timeout()

Para criar um sinal que cancela automaticamente depois de determinado período, use AbortSignal.timeout():

try {
  const response = await fetch('https://api.example.com/status', {
    signal: AbortSignal.timeout(3000)
  });

  console.log(await response.json());
} catch (error) {
  console.error('A operação não terminou a tempo:', error.message);
}

Essa abordagem é mais curta do que criar um controlador e um timer manual apenas para timeout. Ela também evita esquecer de limpar o timer depois que a operação termina.

Um timeout não deve ser escolhido aleatoriamente. Observe latência normal, percentis, dependências externas e objetivo do endpoint. Um valor muito curto transforma pequenas oscilações em falhas; um valor muito longo mantém recursos ocupados quando a dependência já está degradada.

Combinando timeout e cancelamento externo

Uma operação pode ser cancelada tanto pelo usuário quanto por um limite de tempo. AbortSignal.any() cria um sinal que é interrompido quando qualquer um dos sinais de origem é abortado:

async function fetchUser(userId, userSignal) {
  const timeoutSignal = AbortSignal.timeout(5000);
  const signal = AbortSignal.any([
    userSignal,
    timeoutSignal
  ]);

  const response = await fetch(
    `https://api.example.com/users/${userId}`,
    { signal }
  );

  return response.json();
}

Esse padrão permite manter responsabilidades separadas. A rota controla o cancelamento associado ao cliente, enquanto o serviço define o limite máximo que aceita esperar pela dependência.

Motivos de cancelamento com reason

O método abort() aceita um motivo. Esse valor fica disponível em signal.reason:

const controller = new AbortController();

controller.abort(
  new Error('Cliente desconectou antes da resposta')
);

console.log(controller.signal.aborted);
console.log(controller.signal.reason.message);

Motivos facilitam logs, métricas e decisões internas. Em vez de tratar todos os cancelamentos como iguais, a aplicação pode distinguir timeout, desligamento, substituição da tarefa e desconexão do cliente.

Não inclua tokens, senhas ou dados pessoais no motivo. Esse valor pode aparecer em logs ou ser propagado por diferentes camadas.

Interrompendo cedo com throwIfAborted()

Uma função pode verificar o sinal antes de iniciar uma etapa cara:

async function generateReport(signal) {
  signal.throwIfAborted();

  const rows = await loadRows({ signal });

  signal.throwIfAborted();

  return renderReport(rows);
}

Se o sinal já estiver abortado, throwIfAborted() lança o motivo armazenado. Isso evita começar trabalho que já perdeu utilidade e simplifica verificações repetidas.

Cancelando timers baseados em Promises

O módulo node:timers/promises aceita um sinal em operações como setTimeout():

import { setTimeout as delay } from 'node:timers/promises';

const controller = new AbortController();

const wait = delay(10000, 'finalizado', {
  signal: controller.signal
});

setTimeout(() => {
  controller.abort();
}, 500);

try {
  const result = await wait;
  console.log(result);
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Espera cancelada');
  }
}

A documentação oficial de Timers Promises detalha o uso de sinais em timers assíncronos.

Esse recurso é útil para implementar backoff, espera entre tentativas e tarefas periódicas que precisam encerrar rapidamente durante o desligamento da aplicação.

Função sleep cancelável

Com a API de timers, uma função de espera pode aceitar o mesmo sinal usado pelo restante do fluxo:

import { setTimeout as delay } from 'node:timers/promises';

export function sleep(milliseconds, signal) {
  return delay(milliseconds, undefined, { signal });
}
async function retryOperation(signal) {
  for (let attempt = 1; attempt <= 3; attempt++) {
    signal.throwIfAborted();

    try {
      return await callService({ signal });
    } catch (error) {
      if (signal.aborted) throw signal.reason;
      if (attempt === 3) throw error;

      await sleep(attempt * 500, signal);
    }
  }
}

Quando a operação principal é cancelada, a espera entre tentativas também termina. Sem isso, o processo pode continuar dormindo mesmo durante um desligamento.

AbortController em funções próprias

Uma função personalizada deve verificar se o sinal já foi abortado, registrar um listener e remover recursos ao terminar:

function runTask({ signal }) {
  return new Promise((resolve, reject) => {
    signal.throwIfAborted();

    const timer = setTimeout(() => {
      cleanup();
      resolve('concluído');
    }, 5000);

    function onAbort() {
      clearTimeout(timer);
      cleanup();
      reject(signal.reason);
    }

    function cleanup() {
      signal.removeEventListener('abort', onAbort);
    }

    signal.addEventListener('abort', onAbort, {
      once: true
    });
  });
}

O listener usa once: true porque um sinal só é abortado uma vez. A remoção explícita no caminho de sucesso evita manter referências desnecessárias quando a operação termina antes do cancelamento.

Evite vazamento de listeners

Registrar um listener em cada tentativa sem removê-lo pode manter closures, objetos e recursos alcançáveis por mais tempo do que o necessário. A documentação oficial recomenda listeners únicos ou remoção apropriada depois da conclusão.

Em funções que aceitam sinais, adote esta sequência:

  • verifique imediatamente se o sinal já está abortado;
  • registre o listener com once: true;
  • libere timer, socket, stream ou recurso interno no cancelamento;
  • remova o listener quando a operação terminar normalmente;
  • rejeite com o motivo do sinal quando isso fizer sentido.

Cancelando pipelines de streams

Streams são adequadas para arquivos grandes, downloads e transformações progressivas. Uma pipeline pode receber um sinal para encerrar todas as etapas relacionadas:

import fs from 'node:fs';
import { pipeline } from 'node:stream/promises';
import { createGzip } from 'node:zlib';

const controller = new AbortController();

const operation = pipeline(
  fs.createReadStream('./report.csv'),
  createGzip(),
  fs.createWriteStream('./report.csv.gz'),
  { signal: controller.signal }
);

setTimeout(() => controller.abort(), 2000);

try {
  await operation;
} catch (error) {
  if (controller.signal.aborted) {
    console.log('Pipeline interrompida');
  } else {
    throw error;
  }
}

Depois de cancelar uma gravação, verifique se o arquivo parcial precisa ser removido. Para aprofundar o fluxo de dados e o backpressure, consulte Streams no Node.js.

Cancelamento quando o cliente desconecta

Em uma rota HTTP, o servidor pode cancelar operações internas quando a conexão é encerrada antes da resposta:

app.get('/report', async (req, res, next) => {
  const controller = new AbortController();

  req.on('close', () => {
    if (!res.writableEnded) {
      controller.abort(
        new Error('Cliente desconectou')
      );
    }
  });

  try {
    const report = await buildReport({
      signal: controller.signal
    });

    res.json(report);
  } catch (error) {
    if (controller.signal.aborted) return;
    next(error);
  }
});

Essa prática reduz chamadas e cálculos que não produzirão resposta útil. Porém, confirme o comportamento dos eventos do framework e teste conexões encerradas em diferentes fases.

Para estruturar endpoints e middlewares, veja como criar uma API com Node.js.

Desligamento controlado da aplicação

Um controlador global pode representar o encerramento do processo:

const shutdownController = new AbortController();

process.on('SIGTERM', () => {
  shutdownController.abort(
    new Error('Aplicação em encerramento')
  );
});

async function startWorker() {
  const signal = shutdownController.signal;

  while (!signal.aborted) {
    const job = await reserveJob({ signal });
    await processJob(job, { signal });
  }
}

Operações em andamento devem possuir uma política clara: terminar dentro de uma janela, devolver o trabalho à fila ou cancelar imediatamente. O sinal comunica a intenção, mas cada componente precisa limpar recursos e preservar consistência.

AbortController e Worker Threads

O sinal não é compartilhado automaticamente entre threads como um objeto ativo. Para cancelar um worker, envie uma mensagem, use uma estrutura compartilhada apropriada ou encerre a thread quando a política permitir.

controller.signal.addEventListener('abort', () => {
  worker.postMessage({ type: 'cancel' });
}, { once: true });

O worker deve verificar o pedido em pontos seguros. Para tarefas que não cooperam, worker.terminate() encerra a thread inteira, mas pode interromper trabalho no meio. Consulte Worker Threads no Node.js para entender pools, timeouts e limites.

Cancelamento não é rollback

Abortar uma Promise não desfaz automaticamente alterações no banco, mensagens já publicadas ou chamadas remotas concluídas. O cancelamento indica que o chamador não deseja continuar esperando ou processando.

Para operações com efeito permanente:

  • use transações quando forem adequadas;
  • adote chaves de idempotência;
  • registre estados intermediários;
  • confirme o resultado antes de repetir;
  • não suponha que uma conexão cancelada significa operação remota cancelada.

O artigo sobre webhooks seguros com Node.js apresenta estratégias de idempotência e processamento confiável.

Tratando cancelamento e erro separadamente

Um cancelamento esperado não deve gerar o mesmo nível de alerta que uma falha inesperada:

try {
  await executeOperation({ signal });
} catch (error) {
  if (signal.aborted) {
    logger.info({
      reason: signal.reason?.message
    }, 'Operação cancelada');
    return;
  }

  logger.error({ error }, 'Falha na operação');
  throw error;
}

Essa separação melhora métricas e reduz alertas inúteis. Ainda assim, uma taxa anormal de cancelamentos pode indicar timeout curto, clientes instáveis ou dependência lenta.

Observabilidade

Registre dados suficientes para entender o comportamento:

  • tipo da operação;
  • duração antes do cancelamento;
  • motivo;
  • origem do sinal;
  • dependência envolvida;
  • quantidade de bytes ou itens processados;
  • se houve efeito permanente antes do cancelamento.

Crie métricas diferentes para timeout, desconexão do cliente, desligamento e cancelamento manual. Essa divisão ajuda a ajustar limites e identificar gargalos. Combine o recurso com as práticas de performance de APIs Node.js.

Como testar cancelamentos

Testes devem confirmar que a Promise é rejeitada, recursos são liberados e nenhum trabalho continua depois do cancelamento:

import assert from 'node:assert/strict';

const controller = new AbortController();
const task = runTask({ signal: controller.signal });

controller.abort(new Error('teste'));

await assert.rejects(task, error => {
  return error.message === 'teste';
});

Inclua também os seguintes cenários:

  • sinal já abortado antes da função ser chamada;
  • cancelamento durante uma espera;
  • conclusão antes do cancelamento;
  • dois sinais combinados com AbortSignal.any();
  • timeout durante uma requisição HTTP;
  • cancelamento de stream com arquivo parcial;
  • listener removido depois do sucesso;
  • motivo personalizado preservado.

Erros comuns

  • Criar o controller dentro da função errada: a camada chamadora perde a capacidade de cancelar.
  • Ignorar sinal já abortado: a função inicia trabalho desnecessário.
  • Adicionar listeners sem removê-los: referências permanecem vivas e podem aumentar o consumo de memória.
  • Tratar cancelamento como erro crítico: logs e alertas ficam poluídos.
  • Acreditar que abortar faz rollback: efeitos permanentes podem já ter ocorrido.
  • Não propagar o sinal: apenas a primeira camada cancela, enquanto operações internas continuam.
  • Usar timeout sem medir: dependências saudáveis passam a falhar durante pequenas variações.
  • Reutilizar um sinal abortado: ele permanece abortado e não pode ser restaurado.
  • Cancelar sem limpar arquivos ou sockets: recursos parciais ficam abandonados.

Boas práticas para produção

  • Receba signal como opção nas funções assíncronas internas.
  • Mantenha o controlador na camada que decide o ciclo de vida da tarefa.
  • Use AbortSignal.timeout() para limites simples.
  • Use AbortSignal.any() para combinar timeout e cancelamento externo.
  • Chame throwIfAborted() antes de etapas caras.
  • Propague o mesmo sinal para chamadas, timers e streams relacionadas.
  • Use listeners com once: true e remova-os no sucesso.
  • Registre motivos sem incluir dados sensíveis.
  • Separe cancelamentos esperados de falhas reais.
  • Teste desconexão, timeout, desligamento e concorrência.
  • Implemente idempotência quando a operação produzir efeitos externos.
  • Monitore duração e origem dos cancelamentos.

Conclusão

AbortController no Node.js cria um contrato comum para encerrar operações assíncronas que perderam utilidade. Ele funciona com APIs nativas e pode ser adotado por serviços internos, permitindo que fetch, timers, streams e tarefas personalizadas respondam ao mesmo sinal.

Comece adicionando timeout às chamadas HTTP e propagando o sinal para as camadas internas. Depois, combine sinais, registre motivos, trate desconexões e integre o mecanismo ao desligamento da aplicação. Com limpeza de recursos, idempotência e testes adequados, o cancelamento deixa de ser uma exceção improvisada e passa a fazer parte do desenho confiável do sistema.

Os 10 Melhores Cursos de Programação de 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita