Projeto guiado: consumindo uma API com cache simples
Junta closure, event loop e tratamento de erro pra construir um mini cliente de API que não refaz a mesma requisição duas vezes.
Esse projeto junta três coisas que você viu em lições separadas: closure (pra guardar estado sem variável global), tratamento de erro assíncrono de verdade, e AbortController pra cancelar uma requisição que ficou obsoleta. O resultado é um cliente de API pequeno, mas com os mesmos cuidados que um de produção teria.
1. O problema — buscar o mesmo dado de novo, sem necessidade
Se duas partes da tela pedem o mesmo recurso, ou o usuário volta pra uma tela que já visitou, refazer a mesma requisição de rede é desperdício. A solução mais simples: guardar o resultado num cache em memória.
2. Cache em memória com Map, escondido numa closure
function criarClienteApi() {
const cache = new Map(); // fica preso na closure — ninguém fora daqui acessa isso diretamente
return {
// implementado nos próximos passos
};
}cache não é uma variável global — ela só existe dentro do escopo de criarClienteApi. As funções que essa fábrica devolver vão "lembrar" desse cache pra sempre, exatamente como o contador da lição de closures.
3. fetch com tratamento de erro real
async function buscarNaRede(url) {
const resposta = await fetch(url);
if (!resposta.ok) {
throw new Error(`Erro HTTP: ${resposta.status}`);
}
return resposta.json();
}4. Cancelamento — uma busca nova invalida a anterior
function criarClienteApi() {
const cache = new Map();
let controladorAtual = null;
async function buscar(url) {
if (cache.has(url)) {
console.log("Veio do cache:", url);
return cache.get(url);
}
if (controladorAtual) {
controladorAtual.abort(); // cancela uma busca anterior que ainda estava pendente
}
controladorAtual = new AbortController();
try {
const resposta = await fetch(url, { signal: controladorAtual.signal });
if (!resposta.ok) {
throw new Error(`Erro HTTP: ${resposta.status}`);
}
const dado = await resposta.json();
cache.set(url, dado);
return dado;
} catch (erro) {
if (erro.name === "AbortError") {
console.log("Busca cancelada:", url);
return null;
}
console.error("Falha ao buscar", url, erro);
throw erro;
}
}
return { buscar };
}controladorAtual também é uma variável presa na closure — cada chamada de buscar pode ver e cancelar o AbortController da chamada anterior, porque as duas compartilham o mesmo escopo de criarClienteApi.
5. Usando o cliente
const cliente = criarClienteApi();
await cliente.buscar("/api/usuarios/1"); // vai pra rede de verdade
await cliente.buscar("/api/usuarios/1"); // vem do cache — nenhuma requisição nova é feita
// duas telas diferentes usando o MESMO cliente compartilham o cache
const perfil = await cliente.buscar("/api/usuarios/1");Repare que nada disso depende de uma variável global espalhada pelo código: todo o estado (cache, requisição em andamento) vive escondido dentro do cliente que criarClienteApi() devolveu.
Exercício
Adicione um método invalidar(url) ao objeto devolvido por criarClienteApi(), que remove uma única entrada do cache (útil depois de um POST que muda aquele dado no servidor).