Capítulo 21
JSON, fetch e async
5 min de leitura 909 palavras 2 gravuras
Às vezes o dado não está no HTML. Mora em outro arquivo. Depois que a página abriu, o JavaScript pede esse arquivo. O nome antigo disso é AJAX. O verbo moderno no navegador é fetch. Esta é o capítulo 21 de 23.
Nesta etapa o pedido é local: cardapio.json na mesma pasta do exemplo. Não chame API pública para cumprir o exercício. Rede fora da máquina é nota opcional no fim.
O exemplo está em exemplos/21-json-fetch/. Não abra pelo disco (file://). Sirva em 127.0.0.1.
Trabalhe na cópia inicio da etapa. Leia e digite o código; mude uma coisa. Antes de recarregar, anote sua previsão. Depois confira com a referência completa. Se nada mudar, confira o arquivo salvo e o caminho aberto. Blocos adicionais do capítulo podem ser digitados no console; o exemplo completo mostra a aplicação daquela etapa.
O que você vai aprender
- Ler JSON como texto com cara de objeto.
- Usar
JSON.parseeJSON.stringify. - Pedir um arquivo da pasta com
fetch. - Encadear
.thennuma promessa. - Escrever o mesmo fluxo com
async/await. - Distinguir
file://dehttp://127.0.0.1.
JSON é texto combinado
JSON (JavaScript Object Notation) é um formato de mensagem. Frontend e arquivo (ou servidor) combinam o mesmo desenho: chaves entre aspas duplas, sem funções, sem comentários.
{
"nome": "Café coado",
"preco": 8,
"origem": {
"cidade": "Natal",
"uf": "RN"
}
}
Parece objeto JavaScript porque quase sempre é um objeto válido. A resposta da rede, porém, chega como string. Você converte.
Pare e experimente. Abra exemplos/21-json-fetch/cardapio.json. Preveja aspas nas chaves. Recarregue o arquivo no editor. Confira que não há function.
Erro comum: pôr aspas simples nas chaves do JSON. O combinado pede aspas duplas.

parse e stringify
JSON.parse transforma texto em objeto. JSON.stringify faz o caminho inverso.
const texto = '{"nome":"Café coado","preco":8}';
console.log(texto);
const item = JSON.parse(texto);
console.log(item.nome);
console.log(item.preco);
Dentro de aspas de string, um " interno precisa de escape: \". Uma barra vira \\. Ou use crase `…` no JS para montar o texto de teste.
const objeto = { nome: "Tapioca", preco: 12 };
const deNovo = JSON.stringify(objeto);
console.log(deNovo);
console.log(JSON.stringify(objeto, null, 2));
O 2 no terceiro argumento é o recuo. null no segundo você ignora por agora.
Pare e experimente. No console do exemplo, rode JSON.stringify({ ok: true }). Preveja {"ok":true}. Confira.
Erro comum: chamar JSON.parse num objeto que já é objeto. Parse espera string.
fetch no arquivo da pasta
O cardápio do exemplo é uma lista. O script pede o nome relativo à página.
const resposta = await fetch("cardapio.json");
const itens = await resposta.json();
fetch devolve uma promessa: um valor que ainda vai chegar. await só funciona dentro de função async. resposta.json() lê o corpo e já monta o objeto ou o array.
Na pasta curso deste material:
python -m http.server 8771 --bind 127.0.0.1 --directory curso
Visite http://127.0.0.1:8771/exemplos/21-json-fetch/. Encerre com Ctrl+C. 127.0.0.1 é esta máquina. Não use 0.0.0.0. Não publique o JSON.
Pare e experimente. Abra a mesma pasta pelo Explorador (file://) e recarregue. Preveja falha. Volte ao 127.0.0.1. Confira a lista.
Erro comum: duplo clique em index.html e achar que o JSON “não existe”. Ele está no disco. Falta HTTP local.
Promessa com then
Pedido demora. Pode ser rápido ou não. A promessa representa o futuro. .then entrega uma função para quando a resposta chegar.
fetch("cardapio.json")
.then(function (resposta) {
if (!resposta.ok) {
throw new Error("não achei o JSON");
}
return resposta.json();
})
.then(function (itens) {
const lista = document.getElementById("lista");
for (let i = 0; i < itens.length; i += 1) {
const li = document.createElement("li");
li.textContent = itens[i].nome + " — R$ " + itens[i].preco;
lista.append(li);
}
})
.catch(function (falha) {
document.getElementById("lista").textContent = "Abra em 127.0.0.1.";
console.error(falha);
});
Você pode encadear .then. O return da primeira vira o argumento da segunda. .catch pega a falha.
O HTML do exemplo:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Cardápio — Casa da Restinga</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main>
<h1>Cardápio da Restinga</h1>
<ul id="lista"></ul>
</main>
<script src="script.js"></script>
</body>
</html>
Pare e experimente. Renomeie o JSON no disco e recarregue no servidor. Preveja a mensagem de falha. Restaure o nome. Recarregue. Confira os itens.
Erro comum: usar os itens antes do then. O array ainda não chegou.
async e await
A mesma espera, leitura em fila. await pausa aquela função até a promessa resolver.
async function carregar() {
try {
const resposta = await fetch("cardapio.json");
if (!resposta.ok) {
throw new Error("não achei o JSON");
}
const itens = await resposta.json();
const lista = document.getElementById("lista");
itens.forEach(function (item) {
const li = document.createElement("li");
li.textContent = item.nome + " — R$ " + item.preco;
lista.append(li);
});
} catch (falha) {
document.getElementById("lista").textContent = "Abra em 127.0.0.1.";
console.error(falha);
}
}
carregar();
await só dentro de async. Função async sempre devolve uma promessa, mesmo quando o return parece um valor comum.
Pare e experimente. Troque o nome do arquivo no fetch para "nao.json". Preveja o catch. Restaure "cardapio.json". Recarregue. Confira.
Erro comum: await fetch(...) no topo do arquivo, fora de async. O navegador recusa.

O que este volume não exige
O exercício fecha com o JSON da pasta. Não precisa de internet além do servidor local.
Algumas pessoas, depois, brincam com APIs públicas. Um exemplo conhecido é dog.ceo, que devolve foto de cão. Isso é extra opcional, fora da nota deste capítulo. A Casa da Restinga não depende disso. Não abra conta paga. Não publique chave. Não use 0.0.0.0.
Se um dia for além: fetch recebe uma URL, a resposta costuma ser JSON, resposta.json() lê. A lógica é a mesma do cardapio.json.
Pare e experimente. No exemplo, mude o texto de um item no JSON. Preveja o novo nome na lista. Recarregue em 127.0.0.1. Confira. O HTML não mudou: mudou o arquivo de dados.
Erro comum: achar que este capítulo “só funciona com API na nuvem”. O primeiro fetch do volume é local.
Atividades
- Qual a diferença visível entre o texto JSON e o objeto depois de
parse? - Por que
file://costuma quebrar ofetchdeste exemplo? - Onde
awaitpode aparecer? - Este capítulo exige a API dog.ceo?
Respostas comentadas
- JSON é string (ou arquivo de texto). Depois do parse você lê
item.nomecomo chave. - O navegador trata o disco como origem distinta e bloqueia o pedido ao JSON. HTTP em
127.0.0.1é o teste combinado. - Só dentro de função marcada
async. - Não. É nota opcional. O exercício pede
cardapio.jsonna pasta.
MDN
O arquivo completo
Referência do exemplo 21-json-fetch, reproduzida aqui para que você possa conferir sem sair da página.
fetch.js
const lista = document.getElementById("lista");
async function carregar() {
try {
const resposta = await fetch("cardapio.json");
if (!resposta.ok) {
throw new Error("HTTP");
}
const itens = await resposta.json();
lista.innerHTML = "";
for (const item of itens) {
const li = document.createElement("li");
li.textContent = item.nome + " — " + item.preco;
lista.append(li);
}
} catch (erro) {
lista.innerHTML = "";
const li = document.createElement("li");
li.textContent = "Não deu para ler o JSON. Sirva em 127.0.0.1.";
lista.append(li);
}
}
document.getElementById("carregar").addEventListener("click", carregar);
assets/stylesheets/main.css
*,
*::before,
*::after { box-sizing: border-box; }
html, body, h1, h2, h3, p, ul { margin: 0; padding: 0; }
img { max-width: 100%; height: auto; }
body {
font-family: "Segoe UI", Tahoma, sans-serif;
line-height: 1.5;
color: #1a2a2c;
background: #f7f3ea;
}
a { color: #0d5c63; }
a:focus-visible, button:focus-visible, input:focus-visible {
outline: 3px solid #c45c26;
outline-offset: 3px;
}
.skip { position: absolute; left: -999px; }
.skip:focus { left: 0.5rem; top: 0.5rem; background: #fff; padding: 0.4rem 0.7rem; z-index: 2; }
.wrap { width: min(44rem, calc(100% - 2rem)); margin-inline: auto; }
header { background: #0d5c63; color: #fff; padding: 1rem 0; }
header a { color: #fff; }
.brand { font-weight: 700; text-decoration: none; }
nav ul { list-style: none; display: flex; gap: 1rem; margin-top: 0.5rem; }
nav a[aria-current="page"] { text-decoration: underline; text-underline-offset: 0.2em; }
main { padding: 1.5rem 0 3rem; }
footer { border-top: 1px solid #ddd4c4; padding: 1rem 0; font-size: 0.95rem; }
article { margin: 1.25rem 0; padding-bottom: 1rem; border-bottom: 1px solid #ddd4c4; }