Engenharia de IA Aplicada à Programação Web
Aula 7 de 8
Navegar por módulos e aulas
  1. 01 Fundamentos da Web
  2. 02 JavaScript moderno
  3. 03 TypeScript, Git e qualidade
  4. 04 Angular
  5. 05 Node.js, NestJS e APIs
  1. 2.1 Valores, variáveis e tipos
  2. 2.2 Condições e repetições
  3. 2.3 Funções, escopo e closures
  4. 2.4 Arrays e transformação de dados
  5. 2.5 Objetos, JSON e modelagem
  6. 2.6 Módulos e tratamento de erros
  7. 2.7 Promises, async/await e Fetch
  8. 2.8 Armazenamento, testes e projeto

Módulo 2 · Aula 2.7

Promises, async/await e Fetch

2 horas Teoria + laboratório JavaScript moderno
Ver fonte Markdown

Identificação

Introdução

O que significa código síncrono?

No fluxo síncrono, cada instrução termina antes da seguinte:

const title = "Estudar";
const normalizedTitle = title.trim();
console.log(normalizedTitle);

Esse modelo é simples, mas algumas operações levam tempo, como solicitar dados pela rede, aguardar um temporizador ou consultar um banco de dados no servidor. Bloquear a interface durante a espera deixaria a página sem responder.

O que significa assíncrono?

Uma operação assíncrona pode começar agora e terminar depois. O JavaScript continua processando outras tarefas e recebe o resultado futuramente:

console.log("Antes");

setTimeout(() => {
  console.log("Temporizador concluído");
}, 1000);

console.log("Depois");

Saída:

Antes
Depois
Temporizador concluído

Assíncrono significa executar tudo em paralelo?

Não. Assincronismo significa que uma espera não precisa bloquear o fluxo. Paralelismo significa realizar trabalho simultaneamente, normalmente com múltiplas threads, processos ou núcleos.

O navegador coordena rede e temporizadores, enquanto o JavaScript da interface normalmente executa uma tarefa por vez na thread principal.

O que é uma Promise?

Uma Promise representa o resultado futuro de uma operação. Ela possui três estados:

  • pending: aguardando;
  • fulfilled: concluída com valor;
  • rejected: concluída com erro.
const promise = fetch("./data/tasks.json");

promise ainda não é o conteúdo da resposta.

O que é async/await?

É uma sintaxe para consumir Promises com um fluxo legível:

async function loadTasks() {
  const response = await fetch("./data/tasks.json");
  const tasks = await response.json();
  return tasks;
}

await pausa aquela função assíncrona, não o navegador inteiro.

O que é Fetch?

Fetch é uma API do navegador para requisições HTTP:

const response = await fetch("/api/tasks");

Uma resposta HTTP possui status, cabeçalhos e corpo.

fetch rejeita quando recebe 404 ou 500?

Normalmente, não. Um erro HTTP ainda gera um objeto Response. Fetch costuma rejeitar por falha de rede, URL inválida ou cancelamento. Verifique:

if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}

Onde isso aparece no projeto?

O laboratório consumirá JSON pelo servidor local e representará carregamento, sucesso, vazio, erro HTTP, JSON inválido e cancelamento.

Objetivos

Ao final da aula, o aluno deverá conseguir:

  1. diferenciar fluxo síncrono e assíncrono;
  2. explicar os estados de uma Promise;
  3. usar .then, .catch e .finally;
  4. criar funções async;
  5. utilizar await e try...catch;
  6. realizar requisições com Fetch;
  7. verificar response.ok;
  8. interpretar e validar JSON;
  9. representar carregamento, sucesso, vazio e erro;
  10. cancelar operações com AbortController;
  11. diferenciar execução sequencial e concorrente;
  12. impedir respostas obsoletas de atualizar a página.

Pré-requisitos

  • Aulas 2.1 a 2.6 concluídas;
  • funções, módulos, objetos e JSON;
  • tratamento de erros;
  • projeto aberto por http://localhost.

Pergunta orientadora

Como aguardar dados externos sem bloquear a página e sem deixar a interface inconsistente quando algo falha?

Roteiro sugerido

Etapa Duração
Modelo assíncrono e Promises 25 min
async/await e erros 25 min
Fetch e HTTP 30 min
Estados, cancelamento e concorrência 20 min
Laboratório 15 min
Revisão 5 min

1. Callbacks assíncronos

setTimeout(() => {
  console.log("Executado depois");
}, 1000);

O callback é chamado quando o temporizador está pronto e a pilha de execução permite. Callbacks continuam importantes em eventos, mas Promises facilitam compor sequências assíncronas.

2. Criando uma Promise

const waitOneSecond = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Concluído");
  }, 1000);
});
  • resolve cumpre a Promise com valor;
  • reject rejeita com erro.

Na maior parte da aplicação, consumiremos Promises oferecidas por APIs em vez de criá-las manualmente.

3. .then, .catch e .finally

fetch("./data/tasks.json")
  .then((response) => response.json())
  .then((tasks) => renderTasks(tasks))
  .catch((error) => renderError(error))
  .finally(() => setLoading(false));

Cada .then retorna uma nova Promise. .catch trata rejeições anteriores e .finally executa após sucesso ou falha.

4. Funções async

Uma função async sempre retorna uma Promise:

async function getValue() {
  return 42;
}

O valor vira uma Promise cumprida. Se a função lançar um erro, a Promise será rejeitada.

5. await

async function loadTasks() {
  const response = await fetch("./data/tasks.json");
  return response.json();
}

await entrega o valor quando a Promise é cumprida ou lança o motivo da rejeição:

try {
  const tasks = await loadTasks();
  renderTasks(tasks);
} catch (error) {
  renderError(error);
}

6. Esquecer await

const tasks = loadTasks();
console.log(tasks.length);

tasks é uma Promise. Use:

const tasks = await loadTasks();
console.log(tasks.length);

7. Capturar rejeições

Este try não captura uma rejeição futura não aguardada:

try {
  loadTasks();
} catch (error) {
}

Use await:

try {
  await loadTasks();
} catch (error) {
}

Ou .catch(...).

8. Requisição com Fetch

const response = await fetch("./data/tasks.json", {
  method: "GET",
  headers: {
    Accept: "application/json",
  },
});

Para enviar JSON:

await fetch("/api/tasks", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Accept: "application/json",
  },
  body: JSON.stringify(task),
});

9. O objeto Response

Propriedades úteis:

response.ok;
response.status;
response.statusText;
response.headers;
response.url;

ok é verdadeiro para status entre 200 e 299.

10. O corpo da resposta

const data = await response.json();

O corpo normalmente só pode ser consumido uma vez. Outros métodos incluem:

  • text();
  • blob();
  • arrayBuffer();
  • formData().

Escolha conforme o tipo de resposta.

11. JSON inválido

Status 200 não garante JSON válido:

try {
  const data = await response.json();
} catch (error) {
  throw new InvalidResponseError(
    "A resposta não contém JSON válido.",
    { cause: error },
  );
}

Depois, valide a estrutura:

if (!Array.isArray(data)) {
  throw new InvalidResponseError(
    "A resposta deveria conter um array.",
  );
}

12. Estado da interface

Uma operação assíncrona deve representar:

idle → loading → success
               → empty
               → error
               → cancelled

Para cada estado, defina:

  • mensagem;
  • conteúdo visível;
  • controles disponíveis;
  • ação para tentar novamente.

13. Carregamento

function setLoading(isLoading) {
  loadButton.disabled = isLoading;
  cancelButton.disabled = !isLoading;
}

Inclua texto acessível, não apenas animação.

14. Estado vazio

Uma lista vazia pode ser resposta válida:

if (tasks.length === 0) {
  renderEmptyState();
  return;
}

Não apresente “erro” quando o resultado correto é “nenhum item”.

15. Erro HTTP personalizado

export class HttpError extends Error {
  constructor(message, status) {
    super(message);
    this.name = "HttpError";
    this.status = status;
  }
}

Uso:

if (!response.ok) {
  throw new HttpError(
    `HTTP ${response.status}`,
    response.status,
  );
}

16. Cancelamento com AbortController

const controller = new AbortController();

fetch(url, {
  signal: controller.signal,
});

controller.abort();

O cancelamento normalmente produz AbortError:

if (error.name === "AbortError") {
  showCancelled();
  return;
}

É útil quando uma nova busca substitui a anterior ou o usuário abandona a operação.

17. Timeout

Podemos cancelar após um limite:

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

try {
  return await fetch(url, {
    signal: controller.signal,
  });
} finally {
  clearTimeout(timeoutId);
}

18. Sequencial e concorrente

Sequencial:

const tasks = await loadTasks();
const users = await loadUsers();

Concorrente:

const [tasks, users] = await Promise.all([
  loadTasks(),
  loadUsers(),
]);

Use concorrência quando as operações são independentes.

Promise.all rejeita quando uma falha. Promise.allSettled entrega todos os resultados para análise individual.

19. Respostas obsoletas

Se duas buscas forem iniciadas, a primeira pode terminar por último e substituir a mais recente. Estratégias:

  • cancelar a operação anterior;
  • associar um identificador à operação;
  • confirmar que a resposta ainda é a atual antes de renderizar.

O laboratório cancela a operação anterior ao iniciar outra.

20. CORS

CORS é uma política do navegador para requisições entre origens. Origem combina protocolo, host e porta.

Quando uma API não permite a origem da página, a correção costuma estar na configuração da API, não em desativar a segurança do navegador.

O laboratório usa a mesma origem local.

21. Segurança

  • não coloque chaves secretas no front-end;
  • valide todo JSON recebido;
  • use textContent para dados externos;
  • utilize HTTPS em produção;
  • não registre tokens;
  • trate autenticação no servidor;
  • apresente mensagem segura ao usuário.

22. Estrutura do laboratório

assets/
  api.js
  errors.js
  main.js
  styles.css
data/
  tasks.json
  empty.json
  invalid.json

api.js conhece HTTP. main.js conhece DOM e estados. errors.js define tipos de falha.

23. Laboratório guiado

Abra o cliente de API.

Etapa 1 — Sucesso

Selecione Dados disponíveis e carregue. Observe Fetch, status HTTP, JSON e renderização.

Etapa 2 — Vazio

Selecione Lista vazia. O estado deve ser vazio, não erro.

Etapa 3 — HTTP 404

Selecione Recurso inexistente. response.ok deve detectar a falha.

Etapa 4 — JSON inválido

Selecione JSON inválido. O HTTP será 200, mas response.json() falhará.

Etapa 5 — Cancelamento

Selecione Resposta lenta, inicie e clique em Cancelar. A interface deve permitir nova tentativa.

24. Erros comuns

Esquecer await

O código tenta usar a Promise como valor final.

Não verificar response.ok

Um 404 segue pelo fluxo de sucesso e gera mensagem confusa.

Chamar todo erro de “sem internet”

Validação, JSON inválido, 404 e cancelamento possuem causas diferentes.

Não usar finally

O botão pode permanecer desabilitado depois de uma falha.

Expor segredo no navegador

Qualquer valor enviado ao front-end pode ser inspecionado.

Renderizar resposta antiga

Cancele ou identifique operações substituídas.

25. Boas práticas

  • modele todos os estados;
  • verifique status HTTP;
  • valide o formato do JSON;
  • use erros específicos;
  • cancele operações obsoletas;
  • restaure controles em finally;
  • separe HTTP do DOM;
  • ofereça nova tentativa;
  • teste sucesso, vazio, falha e cancelamento;
  • não dependa de serviço externo para exercícios essenciais.

26. Exercícios

Exercício 1 — Espera

Crie wait(milliseconds) que retorne uma Promise.

Exercício 2 — Fetch

Carregue um JSON local, verifique response.ok e confirme que é array.

Exercício 3 — Estados

Implemente idle, loading, success, empty e error.

Exercício 4 — Concorrência

Compare duas esperas sequenciais com Promise.all.

Exercício 5 — Cancelamento

Cancele a requisição anterior ao iniciar uma nova.

27. Desafio

Amplie o laboratório:

  • adicione botão para tentar novamente;
  • configure timeout;
  • apresente duração da operação;
  • carregue dois recursos com Promise.all;
  • use Promise.allSettled para resultado parcial;
  • impeça resposta obsoleta de atualizar o DOM.

28. Lista de verificação de conclusão

  • Sei explicar síncrono e assíncrono.
  • Conheço os estados de uma Promise.
  • Sei usar .then, .catch e .finally.
  • Consigo usar async/await.
  • Verifico response.ok.
  • Trato JSON inválido.
  • Diferencio vazio e erro.
  • Restaurei a interface em finally.
  • Consigo cancelar com AbortController.
  • Não exponho segredos no navegador.

29. Critérios de avaliação

Critério Pontos
Uso correto de Promise e await 20
Verificação HTTP e JSON 20
Estados completos da interface 25
Cancelamento e respostas obsoletas 15
Separação entre API e DOM 10
Acessibilidade e segurança 10
Total 100

Resumo

Nesta aula, aprendemos que:

  • Promises representam resultados futuros;
  • async/await organiza seu consumo;
  • Fetch não rejeita automaticamente erros HTTP;
  • um corpo pode ser inválido mesmo após HTTP 200;
  • interfaces precisam representar carregamento, sucesso, vazio, erro e cancelamento;
  • operações independentes podem ser concorrentes;
  • requisições obsoletas devem ser canceladas ou ignoradas.

Próxima aula

Na Aula 2.8, reuniremos o módulo em um painel final com armazenamento de preferências, testes de regras, importação e exportação, documentação e apresentação.