Português

Aprenda a usar o AbortController do JavaScript para cancelar efetivamente operações assíncronas como requisições fetch, timers e muito mais, garantindo um código mais limpo e performático.

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

No desenvolvimento web moderno, operações assíncronas são onipresentes. Buscar dados de APIs, definir timers e lidar com interações do usuário frequentemente envolve código que é executado independentemente e potencialmente por uma duração estendida. No entanto, existem cenários onde você precisa cancelar essas operações antes que elas sejam concluídas. É aqui que a interface AbortController em JavaScript vem para o resgate. Ela fornece uma maneira limpa e eficiente de sinalizar solicitações de cancelamento para operações DOM e outras tarefas assíncronas.

Entendendo a Necessidade de Cancelamento

Antes de mergulhar nos detalhes técnicos, vamos entender por que cancelar operações assíncronas é importante. Considere estes cenários comuns:

Apresentando AbortController e AbortSignal

A interface AbortController é projetada para resolver o problema de cancelar operações assíncronas. Ela consiste em dois componentes chave:

Uso Básico: Cancelando Requisições Fetch

Vamos começar com um exemplo simples de cancelar uma requisição fetch:


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

fetch('https://api.example.com/data', { signal })
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    console.log('Data:', data);
  })
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch aborted');
    } else {
      console.error('Fetch error:', error);
    }
  });

// Para cancelar a requisição fetch:
controller.abort();

Explicação:

  1. Nós criamos uma instância de AbortController.
  2. Nós obtemos o AbortSignal associado do controller.
  3. Nós passamos o signal para as opções do fetch.
  4. Se precisarmos cancelar a requisição, nós chamamos controller.abort().
  5. No bloco .catch(), nós verificamos se o erro é um AbortError. Se for, nós sabemos que a requisição foi cancelada.

Lidando com AbortError

Quando controller.abort() é chamado, a requisição fetch será rejeitada com um AbortError. É crucial lidar com este erro apropriadamente no seu código. Falhar em fazer isso pode levar a rejeições de promise não tratadas e comportamento inesperado.

Aqui está um exemplo mais robusto com tratamento de erro:


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

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data', { signal });
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    const data = await response.json();
    console.log('Data:', data);
    return data;
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Fetch aborted');
      return null; // Ou lançar o erro para ser tratado mais acima
    } else {
      console.error('Fetch error:', error);
      throw error; // Relança o erro para ser tratado mais acima
    }
  }
}

fetchData();

// Para cancelar a requisição fetch:
controller.abort();

Melhores Práticas para Lidar com AbortError:

Cancelando Timers com AbortSignal

O AbortSignal também pode ser usado para cancelar timers criados com setTimeout ou setInterval. Isto requer um pouco mais de trabalho manual, já que as funções de timer embutidas não suportam diretamente AbortSignal. Você precisa criar uma função customizada que escute pelo sinal de aborto e limpe o timer quando ele for disparado.


function cancellableTimeout(callback, delay, signal) {
  let timeoutId;

  const timeoutPromise = new Promise((resolve, reject) => {
    timeoutId = setTimeout(() => {
      resolve(callback());
    }, delay);

    signal.addEventListener('abort', () => {
      clearTimeout(timeoutId);
      reject(new Error('Timeout Aborted'));
    });
  });

  return timeoutPromise;
}

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


cancellableTimeout(() => {
  console.log('Timeout executed');
}, 2000, signal)
.then(() => console.log("Timeout finished successfully"))
.catch(err => console.log(err));

// Para cancelar o timeout:
controller.abort();

Explicação:

  1. A função cancellableTimeout recebe um callback, um delay e um AbortSignal como argumentos.
  2. Ela configura um setTimeout e armazena o ID do timeout.
  3. Ela adiciona um event listener ao AbortSignal que escuta pelo evento abort.
  4. Quando o evento abort é disparado, o event listener limpa o timeout e rejeita a promise.

Cancelando Event Listeners

Similarmente a timers, você pode usar AbortSignal para cancelar event listeners. Isto é particularmente útil quando você quer remover event listeners associados a um componente que está sendo desmontado.


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

const button = document.getElementById('myButton');

button.addEventListener('click', () => {
  console.log('Button clicked!');
}, { signal });

// Para cancelar o event listener:
controller.abort();

Explicação:

  1. Nós passamos o signal como uma opção para o método addEventListener.
  2. Quando controller.abort() é chamado, o event listener será automaticamente removido.

AbortController em Componentes React

Em React, você pode usar AbortController para cancelar operações assíncronas quando um componente é desmontado. Isso é essencial para prevenir vazamentos de memória e erros causados por atualizar componentes desmontados. Aqui está um exemplo usando o hook useEffect:


import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

    async function fetchData() {
      try {
        const response = await fetch('https://api.example.com/data', { signal });
        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`);
        }
        const data = await response.json();
        setData(data);
      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('Fetch aborted');
        } else {
          console.error('Fetch error:', error);
        }
      }
    }

    fetchData();

    return () => {
      controller.abort(); // Cancela a requisição fetch quando o componente é desmontado
    };
  }, []); // Array de dependência vazio garante que este efeito é executado apenas uma vez na montagem

  return (
    
{data ? (

Data: {JSON.stringify(data)}

) : (

Loading...

)}
); } export default MyComponent;

Explicação:

  1. Nós criamos um AbortController dentro do hook useEffect.
  2. Nós passamos o signal para a requisição fetch.
  3. Nós retornamos uma função de limpeza do hook useEffect. Esta função será chamada quando o componente for desmontado.
  4. Dentro da função de limpeza, nós chamamos controller.abort() para cancelar a requisição fetch.

Casos de Uso Avançados

Encadeando AbortSignals

Às vezes, você pode querer encadear múltiplos AbortSignals juntos. Por exemplo, você pode ter um componente pai que precisa cancelar operações em seus componentes filhos. Você pode conseguir isso criando um novo AbortController e passando seu sinal tanto para o componente pai quanto para os filhos.

Usando AbortController com Bibliotecas de Terceiros

Se você está usando uma biblioteca de terceiros que não suporta diretamente AbortSignal, você pode precisar adaptar seu código para trabalhar com o mecanismo de cancelamento da biblioteca. Isso pode envolver encapsular as funções assíncronas da biblioteca em suas próprias funções que lidam com o AbortSignal.

Benefícios de Usar AbortController

Compatibilidade do Navegador

AbortController é amplamente suportado em navegadores modernos, incluindo Chrome, Firefox, Safari e Edge. Você pode verificar a tabela de compatibilidade no MDN Web Docs para as informações mais recentes.

Polyfills

Para navegadores mais antigos que não suportam nativamente AbortController, você pode usar um polyfill. Um polyfill é um trecho de código que fornece a funcionalidade de um recurso mais novo em navegadores mais antigos. Existem vários polyfills de AbortController disponíveis online.

Conclusão

A interface AbortController é uma ferramenta poderosa para gerenciar operações assíncronas em JavaScript. Ao usar AbortController, você pode escrever código mais limpo, mais performático e mais robusto que lida com o cancelamento de forma elegante. Seja buscando dados de APIs, definindo timers ou gerenciando event listeners, AbortController pode te ajudar a melhorar a qualidade geral de suas aplicações web.

Leitura Adicional