AbortController e Cancelamento de Operações Assíncronas: Do Básico ao Avançado Já leu

AbortController: Dominando o Cancelamento de Operações Assíncronas O é 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. Por que precisamos cancelar operações? 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 resolve exatamente esse problema. Conceitos Fundamentais O que é AbortController? é um objeto que fornece dois componentes: o controlador ( ) e o sinal ( ). O controlador permite enviar um comando de cancelamento, enquanto o sinal é passado para a operação assíncrona que será monitorada. Anatomia

AbortController: Dominando o Cancelamento de Operações Assíncronas

O AbortController é 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.

Por que precisamos cancelar operações?

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 AbortController resolve exatamente esse problema.

Conceitos Fundamentais

O que é AbortController?

AbortController é um objeto que fornece dois componentes: o controlador (AbortController) e o sinal (AbortSignal). O controlador permite enviar um comando de cancelamento, enquanto o sinal é passado para a operação assíncrona que será monitorada.

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

Anatomia básica

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

Usando AbortController com Fetch

Cancelamento manual de requisições

O caso mais comum é cancelar requisições HTTP. Veja como implementar corretamente:

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

Timeout automático com AbortSignal.timeout()

A forma mais elegante é usar AbortSignal.timeout(), que cancela automaticamente após um tempo específico:

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

Padrões Avançados

Gerenciar múltiplas operações com um único sinal

Você pode reutilizar o mesmo sinal para cancelar várias operações em conjunto:

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

Combinar sinais com AbortSignal.any()

Para cenários onde você quer que a primeira operação que terminar vença, use AbortSignal.any():

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

Implementação em uma classe real

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

Conclusão

Três aprendizados principais:

  1. AbortController resolve um problema real: 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.

  2. Use AbortSignal.timeout() para timeouts simples: não precisa de lógica complexa com setTimeout. A API oferece exatamente o que você precisa de forma limpa.

  3. Combine sinais para cenários complexos: AbortSignal.any() permite orquestrar múltiplas operações com elegância, cobrindo casos onde timeout do usuário e timeout automático precisam coexistir.

A prática consistente com essas técnicas transformará suas aplicações em código mais profissional e confiável.

Referências


Artigos relacionados