O que é AJAX?
AJAX significa Asynchronous JavaScript and XML, um termo cunhado em 2005 quando o Gmail e o Google Maps mostraram pela primeira vez que uma página poderia buscar novos dados sem a necessidade de um recarregamento completo. A parte do XML é majoritariamente história — APIs modernas retornam JSON — mas a ideia central permanece: comunicar-se com um servidor em segundo plano e atualizar a página dinamicamente.
A ferramenta original era o XMLHttpRequest. Ele funciona, mas sua API é desajeitada e baseada em callbacks. A substituição moderna é a Fetch API, uma interface baseada em promises disponível em navegadores, Node.js, Deno e runtimes de edge.
Sua primeira requisição fetch
fetch recebe uma URL e retorna uma promise para um Response.
// basic.js
const response = await fetch("https://api.example.com/users");
const users = await response.json();
console.log(users);
Esse é todo o “caminho feliz”. O detalhe é que fetch é resolvida mesmo para códigos de status de erro, portanto, um 404 não lança uma exceção. Você mesmo deve inspecionar a resposta.
O objeto Response
O objeto Response informa o que o servidor enviou de volta.
// response.js
const response = await fetch("/api/users");
response.ok; // true for status 200–299
response.status; // 200, 404, 500, ...
response.statusText;
response.headers.get("content-type");
const data = await response.json(); // parse JSON
const text = await response.text(); // raw text
const blob = await response.blob(); // binary data
const form = await response.formData();
O corpo (body) só pode ser lido uma vez. Se você precisar tanto do texto bruto quanto do JSON analisado, chame response.clone() antes de ler, ou analise o texto manualmente. Note também que response.json() retorna uma rejeição se o corpo não for um JSON válido — outro motivo para envolver as chamadas em try/catch.
Tratando erros adequadamente
Existem dois tipos de falhas, e elas se comportam de maneira diferente.
- Erros de rede — sem conexão, falha de DNS, CORS bloqueado.
fetché rejeitado, entãocatchos trata. - Erros de HTTP — 404, 401, 500.
fetché resolvido; você deve verificarresponse.okouresponse.status.
// errors.js
async function getUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
return await response.json();
} catch (error) {
console.error("Could not load user:", error);
throw error;
}
}
Tratar ambos os casos explicitamente é o que separa um código frágil de um código resiliente. O guia de Tratamento de Erros aborda a estratégia de forma mais ampla.
Enviando dados com POST, PUT e DELETE
Passe um objeto de opções para alterar o método, adicionar headers e anexar um corpo (body).
// create.js
async function createUser(user) {
const response = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(user),
});
if (!response.ok) throw new Error("Could not create user");
return response.json();
}
await createUser({ name: "Ada", role: "engineer" });
Use PUT para substituir um recurso, PATCH para atualizar parte dele e DELETE para removê-lo. Para uploads de arquivos, crie um objeto FormData e passe-o como o body — o navegador definirá o content type multipart correto para você.
Headers e autenticação
Os headers carregam metadados sobre a requisição. Você pode defini-los por requisição, e muitas APIs esperam um token de autorização.
// auth.js
const response = await fetch("/api/me", {
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
},
});
Nunca escreva segredos diretamente (hard-code) no código do lado do cliente — qualquer coisa enviada ao navegador é pública. Os tokens devem vir de um fluxo de login e ser armazenados com cuidado.
Cancelando requisições
Uma requisição que não é mais relevante — o usuário digitou outro caractere, navegou para outra página ou o componente foi desmontado — deve ser cancelada. AbortController faz exatamente isso.
// abort.js
const controller = new AbortController();
fetch("/api/search?q=javascript", { signal: controller.signal })
.then((res) => res.json())
.then(console.log)
.catch((error) => {
if (error.name === "AbortError") return;
console.error(error);
});
// Cancel when it is no longer needed
controller.abort();
Como o fetch não possui uma opção de timeout, o AbortController também é a maneira padrão de implementar um.
// timeout.js
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch("/api/slow", { signal: controller.signal });
return await res.json();
} finally {
clearTimeout(timer);
}
CORS e credenciais
Os navegadores aplicam a same-origin policy: o JavaScript não pode ler respostas de uma origem diferente, a menos que o servidor permita. Essa permissão é o CORS, e ele é configurado inteiramente no servidor por meio de headers de resposta como Access-Control-Allow-Origin. Se você encontrar um erro de CORS, a correção deve ser feita na configuração do servidor, e não na sua chamada de fetch.
Por padrão, fetch não envia cookies para URLs de origens diferentes. Para incluí-los, defina credentials: "include" e certifique-se de que o servidor permite requisições com credenciais. Esta é uma causa comum da confusão do tipo “funciona no Postman, mas não no navegador”.
Padrões práticos
Estados de carregamento, sucesso e erro. Sempre reflita o ciclo de vida da requisição na UI.
// state.js
async function loadPosts() {
showSpinner();
try {
const res = await fetch("/api/posts");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
renderPosts(await res.json());
} catch (error) {
showError("Could not load posts. Try again.");
} finally {
hideSpinner();
}
}
Busca com debounce. Cancele a requisição anterior sempre que uma nova for iniciada, para que respostas lentas não sobrescrevam resultados recentes. Combine AbortController com um pequeno atraso de debounce.
Requisições paralelas. Use Promise.all quando vários endpoints forem necessários simultaneamente, e Promise.allSettled quando um resultado parcial ainda for útil.
Melhores práticas
- Sempre verifique
response.okantes de fazer o parsing do corpo. - Defina um header
Acceptexplícito e umContent-Typeao enviar um corpo. - Envolva requisições com await em
try/catche exiba uma mensagem útil. - Cancele requisições obsoletas com
AbortController. - Mantenha as chamadas de API em um módulo pequeno em vez de espalhar URLs pelos componentes.
- Nunca coloque segredos (secrets) no código do lado do cliente.
- Exiba estados de carregamento e de erro para que a UI nunca pareça quebrada.
Erros comuns
- Assumir que
fetchlança um erro em casos de 404 ou 500. - Esquecer o
JSON.stringifyno corpo de uma requisição. - Esquecer o header
Content-Typee receber um erro de parse do servidor. - Ler o corpo de uma resposta duas vezes.
- Ignorar o CORS até que o navegador bloqueie a requisição em produção.
- Deixar requisições sem cancelamento, permitindo que dados obsoletos vençam uma race condition.
Próximos passos
O Fetch é a ponte entre o seu front end e o mundo. Combine-o com o DOM para renderizar resultados, async/await para sequenciar tarefas e tratamento de erros para lidar com falhas de forma elegante. A partir daí, aprofunde seus conhecimentos sobre métodos, códigos de status e headers, já que eles moldam cada requisição que você envia.