Fetch API: fazendo requisições HTTP

1. Introdução à Fetch API

A Fetch API é a interface moderna do JavaScript para realizar requisições HTTP, substituindo o antigo XMLHttpRequest. Introduzida no ES6, ela utiliza Promises para lidar com operações assíncronas de forma mais limpa e legível.

Diferentemente do XMLHttpRequest, que exigia callbacks aninhados e configuração manual de eventos, o Fetch oferece uma sintaxe concisa baseada em Promises, permitindo encadeamento com .then() ou o uso de async/await. A API é nativa nos navegadores modernos e, a partir do Node.js 18, também está disponível no backend sem necessidade de bibliotecas externas.

Para ambientes legados, existem polyfills como o whatwg-fetch que garantem compatibilidade com navegadores antigos.

2. Sintaxe Básica e Primeira Requisição GET

A estrutura fundamental da Fetch API é surpreendentemente simples:

fetch('https://api.exemplo.com/dados')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Erro:', error));

O método fetch() recebe uma URL e retorna uma Promise que resolve para um objeto Response. Esse objeto contém métodos para extrair o corpo da resposta:

  • .json() — para dados JSON
  • .text() — para texto simples
  • .blob() — para arquivos binários
  • .formData() — para dados de formulário
  • .arrayBuffer() — para buffers binários

Tratamento básico de erros:

fetch('https://api.exemplo.com/usuarios')
  .then(response => {
    if (!response.ok) {
      throw new Error(`Erro HTTP: ${response.status}`);
    }
    return response.json();
  })
  .then(usuarios => console.log(usuarios))
  .catch(error => console.error('Falha na requisição:', error));

A propriedade response.ok retorna true apenas para status entre 200-299, facilitando a detecção de erros HTTP.

3. Configurando Requisições com o Objeto options

O segundo parâmetro do fetch() permite configurar detalhes da requisição:

// POST com JSON
fetch('https://api.exemplo.com/usuarios', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123'
  },
  body: JSON.stringify({
    nome: 'Maria',
    email: 'maria@exemplo.com'
  })
});

Métodos HTTP suportados: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.

Enviando FormData:

const formData = new FormData();
formData.append('arquivo', fileInput.files[0]);
formData.append('nome', 'Documento');

fetch('/upload', {
  method: 'POST',
  body: formData // Não definir Content-Type, o navegador define automaticamente
});

Enviando URLSearchParams:

const params = new URLSearchParams();
params.append('usuario', 'joao');
params.append('acao', 'login');

fetch('/autenticar', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: params
});

4. Tratamento de Respostas e Erros

É crucial entender que o Fetch não rejeita a Promise para erros HTTP (4xx/5xx). A Promise só é rejeitada em caso de falha de rede.

fetch('https://api.exemplo.com/recurso')
  .then(response => {
    if (!response.ok) {
      // Lança exceção manual para erros HTTP
      throw new Error(`Erro ${response.status}: ${response.statusText}`);
    }
    return response.json();
  })
  .then(data => processarDados(data))
  .catch(error => {
    // Captura tanto erros de rede quanto erros HTTP
    console.error('Requisição falhou:', error.message);
  });

Boas práticas: Sempre verifique response.ok e lance exceções apropriadas. Utilize response.status para lógicas condicionais (ex: redirecionar para login se 401).

5. Fetch no Node.js

A partir do Node.js 18, o Fetch está disponível nativamente como globalThis.fetch. Para versões anteriores, use o pacote node-fetch:

npm install node-fetch
// Node.js 18+ (nativo)
const response = await fetch('https://api.exemplo.com/dados');
const data = await response.json();

// Node.js < 18 com node-fetch
import fetch from 'node-fetch';

Diferenças importantes para o ambiente Node.js:
- Timeouts não são nativos — é necessário implementar com AbortController
- Cookies não são gerenciados automaticamente
- Não há suporte a CORS (inexistente no backend)
- O node-fetch não suporta todos os recursos do Fetch do navegador

6. Fetch no Ecossistema React

No React, as requisições geralmente são feitas dentro do hook useEffect:

import { useState, useEffect } from 'react';

function ListaUsuarios() {
  const [usuarios, setUsuarios] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('https://api.exemplo.com/usuarios')
      .then(response => {
        if (!response.ok) throw new Error('Erro ao carregar');
        return response.json();
      })
      .then(data => {
        setUsuarios(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err.message);
        setLoading(false);
      });
  }, []);

  if (loading) return <p>Carregando...</p>;
  if (error) return <p>Erro: {error}</p>;
  return <ul>{usuarios.map(u => <li key={u.id}>{u.nome}</li>)}</ul>;
}

Hook customizado useFetch:

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

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

    fetch(url, { signal: controller.signal })
      .then(res => {
        if (!res.ok) throw new Error('Erro na requisição');
        return res.json();
      })
      .then(setData)
      .catch(err => {
        if (err.name !== 'AbortError') setError(err.message);
      })
      .finally(() => setLoading(false));

    return () => controller.abort(); // Cleanup ao desmontar
  }, [url]);

  return { data, loading, error };
}

7. Boas Práticas e Padrões Avançados

Abortando requisições com AbortController:

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);

try {
  const response = await fetch(url, { signal: controller.signal });
  clearTimeout(timeoutId);
  // processar resposta
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Requisição cancelada por timeout');
  }
}

Wrapper para reutilização de lógica:

const api = {
  baseURL: 'https://api.exemplo.com',

  async request(endpoint, options = {}) {
    const token = localStorage.getItem('token');

    const config = {
      headers: {
        'Content-Type': 'application/json',
        ...(token && { 'Authorization': `Bearer ${token}` }),
        ...options.headers
      },
      ...options
    };

    const response = await fetch(`${this.baseURL}${endpoint}`, config);

    if (!response.ok) {
      const error = await response.json().catch(() => ({}));
      throw new Error(error.message || `Erro ${response.status}`);
    }

    return response.json();
  },

  get(endpoint) { return this.request(endpoint); },
  post(endpoint, data) { return this.request(endpoint, { method: 'POST', body: JSON.stringify(data) }); },
  put(endpoint, data) { return this.request(endpoint, { method: 'PUT', body: JSON.stringify(data) }); },
  delete(endpoint) { return this.request(endpoint, { method: 'DELETE' }); }
};

// Uso:
const usuarios = await api.get('/usuarios');

Cache simples para evitar requisições duplicadas:

const cache = new Map();

async function fetchComCache(url, ttl = 60000) {
  if (cache.has(url)) {
    const { data, timestamp } = cache.get(url);
    if (Date.now() - timestamp < ttl) return data;
  }

  const response = await fetch(url);
  const data = await response.json();
  cache.set(url, { data, timestamp: Date.now() });
  return data;
}

8. Conclusão e Próximos Passos

A Fetch API representa a evolução natural das requisições HTTP no JavaScript, oferecendo uma interface baseada em Promises que se integra perfeitamente com async/await. Embora bibliotecas como Axios ofereçam recursos adicionais (interceptors automáticos, progresso de upload, timeouts nativos), o Fetch é suficiente para a maioria dos casos de uso e elimina dependências externas.

Pontos-chave para lembrar:
- Sempre verifique response.ok para erros HTTP
- Use AbortController para cancelar requisições
- No React, gerencie estados de loading, dados e erro
- Considere criar wrappers para centralizar lógica de autenticação

Nos próximos artigos, exploraremos o consumo de APIs REST completas, manipulação de JSON e envio de formulários complexos.


Referências