A Fetch API é uma interface moderna para realizar requisições HTTP no navegador, substituindo o antigo XMLHttpRequest. Ela fornece uma abordagem baseada em Promises, tornando o código mais limpo e fácil de gerenciar. Nesta aula, vamos explorar os principais conceitos e práticas para usar a Fetch API em aplicações web.

Com a Fetch API, você pode buscar recursos (como dados JSON, textos ou imagens) de servidores remotos e enviar dados para eles. Vamos cobrir desde requisições simples até o tratamento de erros de rede, passando por configurações de cabeçalhos e manipulação de respostas.

GET e POST

Requisições GET são usadas para obter dados do servidor, enquanto POST é usado para enviar dados (como formulários ou JSON) para serem processados. A Fetch API simplifica ambos os tipos.

Exemplo de GET:

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

Exemplo de POST enviando JSON:

fetch('https://api.exemplo.com/enviar', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ nome: 'João', idade: 30 })
})
  .then(response => response.json())
  .then(data => console.log('Sucesso:', data))
  .catch(error => console.error('Erro:', error));

No POST, é obrigatório especificar o método e, geralmente, o cabeçalho Content-Type para que o servidor interprete corretamente o corpo da requisição. O corpo deve ser convertido para string com JSON.stringify().

Headers

Os cabeçalhos (headers) permitem enviar metadados adicionais na requisição, como tokens de autenticação, tipo de conteúdo esperado, etc. Você pode configurá-los no objeto de opções da Fetch.

Exemplo com headers personalizados:

fetch('https://api.exemplo.com/protegido', {
  headers: {
    'Authorization': 'Bearer meu-token',
    'Accept': 'application/json'
  }
})
  .then(response => response.json())
  .then(data => console.log(data));

É possível também usar o construtor Headers para criar e manipular cabeçalhos de forma mais programática:

const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('X-API-Key', 'chave-secreta');

fetch('https://api.exemplo.com/dados', {
  method: 'POST',
  headers: headers,
  body: JSON.stringify({ chave: 'valor' })
});

Headers comuns incluem Content-Type, Accept, Authorization, Cache-Control, entre outros. Lembre-se de que alguns cabeçalhos são restritos e não podem ser modificados por segurança (como Cookie ou Host).

Tratando respostas

A resposta da Fetch é um objeto Response que contém informações como status, headers e corpo. Você precisa decidir como processar o corpo, dependendo do formato (JSON, texto, blob, etc.).

Exemplo verificando o status:

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

Propriedades úteis do objeto Response:

  • response.status – código numérico (200, 404, etc.)
  • response.statusText – mensagem correspondente (OK, Not Found)
  • response.ok – booleano indicando se status está entre 200-299
  • response.headers – objeto Headers com os cabeçalhos da resposta

Métodos para extrair o corpo:

  • response.json() – retorna Promise com o JSON analisado
  • response.text() – retorna Promise com o texto puro
  • response.blob() – para dados binários (imagens, arquivos)
  • response.formData() – para dados de formulário

Importante: o corpo só pode ser lido uma vez. Se precisar do conteúdo em diferentes formatos, use response.clone().

Erros de rede

Diferente de requisições com XMLHttpRequest, a Fetch API só rejeita a Promise em caso de erros de rede (como falha de DNS, tempo limite excedido ou conexão recusada). Erros HTTP (404, 500) são considerados respostas bem-sucedidas no nível da rede, então a Promise é resolvida normalmente. Você precisa verificar manualmente o status da resposta.

Exemplo tratando erros de rede e HTTP:

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

Para detectar erros de rede, você pode verificar se o erro é do tipo TypeError (comum em falhas de rede). Além disso, você pode usar o AbortController para cancelar requisições e lidar com timeouts:

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

fetch('https://api.exemplo.com/dados', { signal: controller.signal })
  .then(response => response.json())
  .then(data => {
    clearTimeout(timeoutId);
    console.log(data);
  })
  .catch(error => {
    clearTimeout(timeoutId);
    if (error.name === 'AbortError') {
      console.error('Requisição cancelada por timeout');
    } else {
      console.error('Erro:', error);
    }
  });

Esse padrão é útil para evitar requisições pendentes que podem travar a aplicação.

Boas práticas

  • Sempre verifique response.ok antes de processar o corpo.
  • Use try/catch com async/await para um código mais legível.
  • Defina timeouts com AbortController para evitar esperas infinitas.
  • Não exponha tokens ou chaves no frontend; use variáveis de ambiente ou proxies.
  • Prefira usar bibliotecas como axios para funcionalidades extras, mas a Fetch nativa é suficiente para a maioria dos casos.

Referências

Exercícios

  1. Faça uma requisição GET para 'https://jsonplaceholder.typicode.com/posts/1' e exiba o título do post no console.

    ✓ Resposta:
    fetch('https://jsonplaceholder.typicode.com/posts/1')
      .then(response => response.json())
      .then(data => console.log(data.title))
      .catch(error => console.error('Erro:', error));
  2. Envie um POST para 'https://jsonplaceholder.typicode.com/posts' com um objeto JSON contendo title, body e userId. Exiba a resposta no console.

    ✓ Resposta:
    fetch('https://jsonplaceholder.typicode.com/posts', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        title: 'Meu post',
        body: 'Conteúdo do post',
        userId: 1
      })
    })
      .then(response => response.json())
      .then(data => console.log(data))
      .catch(error => console.error('Erro:', error));
  3. Faça uma requisição GET com um cabeçalho personalizado 'X-Custom-Header' de valor 'meu-valor' para 'https://httpbin.org/headers' e exiba a resposta.

    ✓ Resposta:
    fetch('https://httpbin.org/headers', {
      headers: {
        'X-Custom-Header': 'meu-valor'
      }
    })
      .then(response => response.json())
      .then(data => console.log(data))
      .catch(error => console.error('Erro:', error));
  4. Trate uma resposta com status 404: faça uma requisição GET para uma URL inexistente (ex.: 'https://jsonplaceholder.typicode.com/invalid') e verifique se response.ok é false, lançando um erro com o status.

    ✓ Resposta:
    fetch('https://jsonplaceholder.typicode.com/invalid')
      .then(response => {
        if (!response.ok) {
          throw new Error(`Erro HTTP: ${response.status}`);
        }
        return response.json();
      })
      .then(data => console.log(data))
      .catch(error => console.error('Erro:', error.message));
  5. Simule um erro de rede usando uma URL inválida (ex.: 'https://dominioquenaoexiste123.com') e capture o erro, exibindo 'Erro de rede' no console.

    ✓ Resposta:
    fetch('https://dominioquenaoexiste123.com')
      .then(response => console.log(response))
      .catch(error => {
        if (error instanceof TypeError) {
          console.error('Erro de rede');
        } else {
          console.error('Erro:', error.message);
        }
      });