Fetch: um cliente HTTP baseado em Promises
A fetch já vem embutida nos navegadores e nas versões modernas do Node. Você passa uma URL, e ela devolve uma Promise que resolve para um objeto Response. No fundo, é essa a API inteira:
Dois .then em sequência porque existem duas etapas assíncronas: primeiro chegam os cabeçalhos da resposta (é isso que a primeira promise entrega), e só depois o corpo é lido e convertido (response.json() também é uma promise). O corpo só é baixado quando você pede.
O mesmo fluxo com async/await fica parecendo código comum, lido de cima para baixo:
Dois await, dois pontos de suspensão. O mesmo trabalho, só que bem mais fácil de ler de cima pra baixo.
O objeto Response
O que a fetch devolve não é o corpo da resposta — é um objeto Response, que traz os metadados e os métodos para você ler esse corpo em diferentes formatos:
Você pode ler o corpo da resposta usando .json(), .text(), .blob(), .arrayBuffer() ou .formData(). Cada um desses métodos retorna uma promise. E tem um detalhe importante: o corpo só pode ser lido uma vez — se você chamar .json() duas vezes na mesma resposta, a segunda chamada vai estourar um erro.
A maior pegadinha: erros HTTP não rejeitam a promise
Essa aqui confunde praticamente todo mundo que está começando com o fetch. Uma resposta 404 ou 500 não é uma rejeição. A promise resolve normalmente, só que com response.ok === false. O fetch só rejeita quando a requisição em si não conseguiu completar — falha de DNS, sem conexão de rede ou bloqueio por CORS.
Na prática, isso significa que um fetch ingênuo vai aceitar numa boa uma página de erro e só vai quebrar depois, na hora do .json():
A solução é verificar response.ok manualmente e lançar um erro quando o servidor retornar um status de erro:
Acostume-se a escrever esse bloco if (!response.ok). Ele merece um lugar em todo wrapper de fetch que você criar.
Fazendo uma requisição POST com fetch
GET é o método padrão. Para qualquer outro verbo HTTP, basta passar um segundo argumento — um objeto de opções:
Três coisas que vale a pena destacar:
methodtem como padrão"GET". Defina explicitamente quando for POST, PUT, DELETE ou PATCH.bodyaceita uma string (ouFormData,Blob, etc.) — o fetch não serializa objetos automaticamente para você. OJSON.stringify(...)é responsabilidade sua.- O header
Content-Typediz ao servidor como interpretar o corpo da requisição. Se esquecer, a maioria dos servidores vai tratar o body como texto puro.
Headers, query strings e outras opções do fetch
Os headers podem ser um objeto comum (ou uma instância de Headers). Já a query string você monta na mão, normalmente com URLSearchParams:
URLSearchParams cuida da codificação pra você — espaços, &, unicode — assim você não acaba com URLs quebradas quando a entrada tem caracteres que precisam ser escapados.
Outras opções que você vai ver em código real: credentials: "include" pra enviar cookies em requisições cross-origin, cache: "no-store" pra ignorar o cache HTTP, e mode: "cors" (que normalmente é o padrão) pra controlar o comportamento de CORS.
Cancelando uma requisição com AbortController
Às vezes você precisa desistir da requisição — o usuário digitou uma nova busca, ou a chamada está demorando demais. É aí que entra o AbortController:
controller.abort() faz com que a promise do fetch seja rejeitada com uma DOMException cujo name é "AbortError". Já o bloco finally limpa o timeout para que uma requisição bem-sucedida não deixe um timer pendurado por aí.
Esse padrão — fetch com timeout e limpeza — vale a pena ser encapsulado em um helper para reutilizar em todo lugar.
Um wrapper reutilizável
Juntando tudo, dá pra montar um pequeno helper que cuida do boilerplate de uma vez só:
Um único lugar pra ajustar headers, um único lugar pra tratar erros, um único lugar pra lidar com respostas vazias. Todo app minimamente sério acaba chegando em algo parecido com isso.
A seguir: tratamento de erros em código assíncrono
O fetch é um dos pontos onde erros assíncronos mais aparecem, e a verificação do response.ok é só uma das peças do quebra-cabeça. A próxima página fala sobre tratamento de erros em promises e async/await — pra onde os erros vão, como capturá-los e as armadilhas que deixam eles passarem despercebidos.
Perguntas frequentes
Como usar o fetch no JavaScript?
Basta chamar fetch(url) passando a URL desejada. Ele retorna uma Promise que resolve em um objeto Response. Para ler o corpo como JSON, chame response.json() (que também é uma promise). Com async/await fica assim: const res = await fetch(url); const data = await res.json();.
Como fazer uma requisição POST com fetch?
Passe um segundo argumento no fetch com method: 'POST', um objeto headers (normalmente 'Content-Type': 'application/json') e o body. Lembre de converter objetos em string com JSON.stringify(...) — o fetch não serializa o corpo automaticamente pra você.
Por que o fetch não dispara erro em status 404 ou 500?
O fetch só rejeita a promise em falhas de rede — erros de DNS, sem conexão, bloqueio de CORS. Para ele, uma resposta HTTP com status de erro ainda é uma resposta válida. Cabe a você checar response.ok (true entre 200 e 299) ou response.status e lançar um erro manualmente quando o servidor retornar algo inesperado.
Dá para cancelar uma requisição fetch?
Dá sim, usando o AbortController. Você cria uma instância, passa o signal dela no objeto de opções do fetch e chama controller.abort() quando quiser cancelar. A promise do fetch vai rejeitar com um AbortError, que você trata normalmente no catch.