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

AbortController no Node.js

Atualizado em: 29 de setembro de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

AbortController oferece um padrão comum para cancelar operações assíncronas no Node.js. Ele pode interromper Fetch, timers, streams, processos filhos e várias APIs que aceitam AbortSignal. O objetivo não é apenas reduzir latência: cancelamento libera sockets, arquivos, memória e capacidade de serviços dependentes.

Sem cancelamento, uma requisição que já expirou pode continuar consultando banco, chamando APIs e processando dados. Em picos, esse trabalho inútil aumenta a sobrecarga e pode criar uma cascata de timeouts.

Conceitos básicos

const controller = new AbortController();
const { signal } = controller;

signal.addEventListener('abort', () => {
  console.log('Operação cancelada', signal.reason);
});

controller.abort(new Error('Cancelamento solicitado'));

O controller emite o cancelamento; o signal é passado para as operações. Um signal só muda do estado não abortado para abortado e não pode ser reutilizado para uma nova tentativa.

Verificando o estado

if (signal.aborted) {
  throw signal.reason;
}

signal.throwIfAborted();

throwIfAborted é útil antes de iniciar uma etapa cara e dentro de loops cooperativos.

Timeout com AbortSignal

const signal = AbortSignal.timeout(5_000);

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

O timeout cancela a operação depois do intervalo. Trate o erro sem confundir timeout, cancelamento do cliente e falha de rede.

Fetch com timeout e tratamento

async function fetchJson(url, timeoutMs = 5000) {
  const signal = AbortSignal.timeout(timeoutMs);

  try {
    const response = await fetch(url, { signal });
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    if (signal.aborted) {
      throw new Error(`Timeout após ${timeoutMs} ms`, { cause: error });
    }
    throw error;
  }
}

Defina também limites para o corpo. Um servidor pode responder headers rapidamente e enviar dados indefinidamente.

Combinando sinais

Uma operação pode ser cancelada pelo timeout ou pelo encerramento da requisição:

const timeoutSignal = AbortSignal.timeout(10_000);
const signal = AbortSignal.any([
  requestSignal,
  timeoutSignal,
]);

await fetch(url, { signal });

Confirme a versão mínima do Node.js usada pelo projeto. Quando AbortSignal.any não estiver disponível, crie um controller e propague eventos manualmente.

Cancelamento em servidores HTTP

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);
  }
});

O evento exato e a condição de desconexão dependem do framework. Teste requests concluídas normalmente para não abortar após a resposta válida.

Propagando o signal

async function buildReport({ signal }) {
  signal.throwIfAborted();

  const users = await loadUsers({ signal });
  const payments = await loadPayments({ signal });

  signal.throwIfAborted();
  return merge(users, payments);
}

Não crie um novo timeout em cada camada sem coordenação. Propague um orçamento de tempo ou o mesmo signal, adicionando limites locais quando necessário.

Timers canceláveis

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

const controller = new AbortController();

const task = setTimeout(30_000, 'concluído', {
  signal: controller.signal,
});

controller.abort();

try {
  await task;
} catch (error) {
  console.log(error.name);
}

Timers tradicionais com callback continuam usando clearTimeout. A versão Promise integra melhor com AbortSignal.

Streams e pipeline

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

await pipeline(source, transform, destination, {
  signal,
});

Ao abortar, pipeline destrói as streams compatíveis. Implemente limpeza em streams personalizadas e remova arquivos parciais.

Processos filhos

import { spawn } from 'node:child_process';

const child = spawn('programa', ['--modo', 'batch'], {
  signal,
});

O signal envia o cancelamento ao processo, mas processos descendentes e diferenças de plataforma exigem testes. Aplique prazo de encerramento.

fs e operações de arquivo

Algumas APIs de arquivo aceitam signal. O cancelamento pode impedir etapas adicionais, mas não garante interromper imediatamente toda chamada de sistema já em andamento. Consulte o contrato de cada função.

Função própria cancelável

async function processItems(items, { signal }) {
  for (const item of items) {
    signal.throwIfAborted();
    await processItem(item);
  }
}

Esse é cancelamento cooperativo: a função verifica o signal em pontos seguros. Em loops CPU-bound, verifique periodicamente, mas não a cada instrução.

Promise com evento de abort

function waitForMessage(emitter, { signal }) {
  return new Promise((resolve, reject) => {
    function cleanup() {
      emitter.off('message', onMessage);
      signal.removeEventListener('abort', onAbort);
    }

    function onMessage(message) {
      cleanup();
      resolve(message);
    }

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

    if (signal.aborted) {
      reject(signal.reason);
      return;
    }

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

Remova listeners ao concluir para evitar vazamentos e resolução duplicada.

Reason

Passe uma razão útil:

controller.abort({
  code: 'CLIENT_DISCONNECTED',
  requestId,
});

Prefira Error ou um objeto pequeno. Não inclua dados pessoais. Bibliotecas podem esperar uma exceção padrão, então mantenha interoperabilidade.

Erro de timeout

Não transforme todo abort em HTTP 500. Exemplos de classificação:

  • cliente desconectou: não há resposta a enviar;
  • timeout interno: 504 ou erro de dependência;
  • shutdown: interromper e permitir retry;
  • cancelamento manual: resultado específico da operação.

Em logs, diferencie cancelamentos esperados de falhas inesperadas.

Deadline

Em sistemas distribuídos, um deadline absoluto pode ser mais útil que vários timeouts independentes:

const remainingMs = Math.max(0, deadline - Date.now());
const signal = AbortSignal.timeout(remainingMs);

Valide deadlines recebidos e reserve margem para serialização e resposta.

Retries

Cada tentativa deve respeitar o signal global:

async function retry(operation, { signal, attempts = 3 }) {
  let lastError;

  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    signal.throwIfAborted();
    try {
      return await operation({ signal, attempt });
    } catch (error) {
      lastError = error;
      if (signal.aborted || attempt === attempts) throw error;
      await setTimeout(100 * attempt, undefined, { signal });
    }
  }

  throw lastError;
}

Não faça retry de operações não idempotentes sem chave de idempotência.

Promise.all

Quando uma operação falha, Promise.all não cancela automaticamente as restantes. Use um controller compartilhado:

const controller = new AbortController();

try {
  return await Promise.all([
    loadA({ signal: controller.signal }),
    loadB({ signal: controller.signal }),
  ]);
} catch (error) {
  controller.abort(error);
  throw error;
}

Worker Threads

AbortSignal não é um botão universal para interromper cálculo em outra thread. Envie mensagem de cancelamento ou use SharedArrayBuffer com flag. O worker precisa cooperar.

Shutdown

Crie um controller global para tarefas de longa duração:

const shutdownController = new AbortController();

process.once('SIGTERM', () => {
  shutdownController.abort(new Error('Servidor encerrando'));
});

Não misture signal global e signal por requisição sem combinar corretamente.

Observabilidade

Meça quantidade de cancelamentos por motivo, duração até cancelamento, timeouts por dependência e trabalho interrompido. Evite alertar como erro todo cancelamento de cliente.

Testes

Teste signal já abortado, abort durante I/O, operação concluída antes do abort, múltiplos listeners, timeout e limpeza de recursos. Use timers curtos controlados, não testes frágeis dependentes de rede externa.

Erros comuns

  • criar timeout sem propagar signal;
  • reutilizar signal abortado;
  • não limpar listeners;
  • tratar abort como erro 500;
  • achar que Promise.all cancela tarefas;
  • cancelar a Promise sem cancelar o recurso;
  • ignorar arquivos e respostas parciais;
  • usar retries depois do deadline;
  • não testar desconexão do cliente.

Fluxo recomendado

Defina um orçamento, crie ou combine signals na borda, propague por todas as camadas, implemente limpeza e classifique razões. Combine com Streams e Backpressure, Web Streams API, child_process e Worker Threads.

Consulte a documentação oficial de AbortController e a referência oficial de AbortSignal.

10 melhores cursos de programação em 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