JavaScript Assíncrono
Aula 1 — Por que precisamos de programação assíncrona?
Nas aulas anteriores, aprendemos a utilizar JavaScript para tornar páginas Web interativas. Trabalhamos com funções, estruturas de controle, arrays, manipulação do DOM e eventos.
Esses conhecimentos permitem construir aplicações que respondem às ações do usuário e modificam dinamicamente a página.
Entretanto, existe um problema que ainda não enfrentamos:
Como obter informações que não estão disponíveis na própria página ou no computador do usuário?
Imagine uma aplicação Web que precise apresentar:
- A previsão do tempo de uma cidade;
- A cotação de uma moeda;
- Os dados de um produto;
- Informações sobre um filme;
- Notícias;
- Os dados de um Pokémon.
Essas informações podem estar armazenadas em outro computador, em um servidor localizado em algum lugar da Internet.
Nesse momento, nosso programa deixa de trabalhar apenas com informações que já possui e passa a precisar se comunicar com outros sistemas.
É nesse contexto que surge a programação assíncrona.
Nesta aula, vamos compreender o problema que torna a programação assíncrona necessária. Ainda não estudaremos Promise, async, await ou fetch() em profundidade. Primeiro precisamos compreender o problema que essas tecnologias ajudam a resolver.
Objetivos
Ao final desta aula, você deverá ser capaz de:
- Diferenciar uma execução sequencial de uma operação assíncrona;
- Explicar por que uma operação externa pode levar tempo para ser concluída;
- Compreender, em nível introdutório, a relação entre cliente e servidor;
- Identificar o problema do tempo de resposta em uma aplicação Web;
- Compreender por que JavaScript precisa trabalhar com operações assíncronas;
- Preparar-se para o estudo de callbacks e Promises.
Projeto da disciplina
Durante as próximas aulas, esses conceitos serão aplicados na construção de uma Pokédex Assíncrona, uma aplicação Web capaz de consultar dados de Pokémon em uma API e apresentá-los dinamicamente na página.
O projeto será desenvolvido progressivamente, acompanhando a evolução dos conceitos estudados.
O PROBLEMA DOS DADOS EXTERNOS
1. Quando os dados não estão na página
Até agora, grande parte das aplicações que construímos utilizava dados que já estavam disponíveis no próprio programa.
Por exemplo:
const nome = 'Pikachu';
console.log(nome);
O programa já conhece a informação.
Também podemos trabalhar com uma estrutura de dados previamente criada:
const pokemon = {
nome: 'Pikachu',
tipo: 'Elétrico'
};
console.log(pokemon.nome);
Nesse caso, os dados estão disponíveis imediatamente para o JavaScript.
Agora imagine uma situação diferente.
O usuário deseja consultar os dados de um Pokémon, mas nossa aplicação não possui essas informações armazenadas.
Onde estão os dados?
Eles podem estar em um servidor.
Podemos representar a situação assim:
┌─────────────────────────┐
│ NAVEGADOR │
│ │
│ Aplicação Web │
│ JavaScript │
└────────────┬────────────┘
│
│ Solicitação
▼
┌─────────────────────────┐
│ SERVIDOR │
│ │
│ Dados dos Pokémon │
└─────────────────────────┘
O navegador precisa solicitar os dados ao servidor.
O servidor, por sua vez, precisa processar essa solicitação e devolver uma resposta.
Portanto, surge uma nova situação:
Aplicação
↓
Solicitação
↓
Servidor
↓
Processamento
↓
Resposta
↓
Aplicação
Esse processo não acontece necessariamente de forma instantânea.
A comunicação pela rede pode levar algum tempo.
Além disso, a operação pode apresentar problemas.
O servidor pode estar indisponível.
A conexão pode falhar.
O recurso solicitado pode não existir.
A resposta pode demorar.
Portanto, o JavaScript precisa saber trabalhar com uma situação fundamental:
O resultado de uma operação pode não estar disponível no momento em que a operação é iniciada.
É nesse ponto que começa o estudo da programação assíncrona.
EXECUÇÃO SEQUENCIAL
2. Como o JavaScript executa instruções?
Considere o código:
console.log('Início');
console.log('Processando');
console.log('Fim');
A saída será:
Início
Processando
Fim
As instruções são executadas em sequência.
Podemos representar:
Início
↓
Processando
↓
Fim
O programa chega a uma instrução, executa essa instrução e prossegue para a próxima.
Esse comportamento é simples de observar.
Agora imagine que uma das operações precise de algum tempo para ser concluída.
Por exemplo:
Início
↓
Consultar servidor
↓
Esperar resposta
↓
Processar resposta
↓
Fim
Se o programa precisasse simplesmente interromper toda a execução enquanto aguardasse a resposta, uma operação demorada poderia prejudicar a experiência do usuário.
Precisamos, portanto, de uma forma de lidar com operações cujo resultado será obtido posteriormente.
PRIMEIRO EXPERIMENTO ASSÍNCRONO
3. Uma operação que termina depois
Observe o código:
console.log('Início');
setTimeout(() => {
console.log('Operação concluída');
}, 3000);
console.log('Fim');
Ao executar o programa, a saída será aproximadamente:
Início
Fim
Operação concluída
Observe atentamente a ordem.
O código foi escrito nesta sequência:
1. Início
2. Operação concluída
3. Fim
Entretanto, a saída foi:
1. Início
2. Fim
3. Operação concluída
Por que isso aconteceu?
A instrução:
setTimeout(() => {
console.log('Operação concluída');
}, 3000);
solicita ao navegador que execute aquela função posteriormente.
O valor 3000 representa aproximadamente três segundos.
Enquanto esse período passa, o JavaScript continua a execução das instruções seguintes.
Por isso:
console.log('Fim');
é executado antes de:
console.log('Operação concluída');
Podemos representar o comportamento assim:
Início
↓
Agenda operação
↓
Fim
↓
[algum tempo depois]
↓
Operação concluída
O setTimeout() está sendo utilizado aqui como uma simulação didática de uma operação que demora para produzir seu resultado.
Uma requisição a um servidor é um exemplo de operação real que pode apresentar esse comportamento temporal.
EXPERIMENTO COM setTimeout(..., 0)
4. Zero milissegundos significa imediatamente?
Observe:
console.log('1');
setTimeout(() => {
console.log('2');
}, 2000);
console.log('3');
setTimeout(() => {
console.log('4');
}, 0);
console.log('5');
Antes de executar o programa, faça uma previsão.
Qual será a ordem dos números?
Depois execute o código.
O resultado esperado é:
1
3
5
4
2
Observe especialmente:
setTimeout(() => {
console.log('4');
}, 0);
O tempo especificado foi:
0 milissegundos
Mesmo assim, 4 não aparece antes de 3 e 5.
Isso ocorre porque setTimeout() não significa:
“Execute esta função imediatamente.”
Ele significa, de forma simplificada:
“Agende esta função para execução posterior, respeitando pelo menos o atraso especificado.”
Portanto:
setTimeout(funcao, 0);
não significa execução imediata.
A função será executada posteriormente, quando o ambiente de execução puder processá-la.
O QUE É PROGRAMAÇÃO ASSÍNCRONA?
5. Uma primeira definição
Podemos definir programação assíncrona, em nível introdutório, da seguinte maneira:
Programação assíncrona é uma forma de trabalhar com operações cuja conclusão pode ocorrer posteriormente, permitindo que a execução prossiga enquanto o resultado ainda não está disponível.
Compare:
Execução sequencial
A
↓
B
↓
C
↓
D
Operação assíncrona
A
↓
Inicia operação
↓
C
↓
D
\
\
Resultado posteriormente
O ponto fundamental é:
Assíncrono não significa mais rápido.
Uma operação assíncrona pode continuar levando três segundos, dez segundos ou mais.
A diferença está na forma como o programa organiza sua execução enquanto aguarda o resultado.
Na Web, isso é particularmente importante porque muitas operações dependem de recursos externos:
- Servidores;
- Redes;
- APIs;
- Bancos de dados;
- Arquivos;
- Serviços Web.
CLIENTE E SERVIDOR
6. Como uma aplicação Web obtém dados?
Quando uma aplicação precisa de dados que estão em outro sistema, ocorre uma comunicação entre um cliente e um servidor.
No nosso projeto, o cliente será o navegador.
┌───────────────────────┐
│ CLIENTE │
│ │
│ Navegador │
│ HTML │
│ CSS │
│ JavaScript │
└───────────┬───────────┘
│
│ Requisição
▼
┌───────────────────────┐
│ SERVIDOR │
│ │
│ API │
│ │
│ Dados │
└───────────┬───────────┘
│
│ Resposta
▼
┌───────────────────────┐
│ CLIENTE │
│ │
│ Processa os dados │
│ Atualiza a interface │
└───────────────────────┘
Podemos resumir o processo:
Cliente
↓
Requisição
↓
Servidor
↓
Processamento
↓
Resposta
↓
Cliente
No projeto da Pokédex:
Navegador
↓
JavaScript
↓
PokéAPI
↓
Dados do Pokémon
↓
JavaScript
↓
DOM
↓
Card do Pokémon
Portanto, nossa aplicação precisará aprender a fazer algo que ainda não fizemos:
Solicitar dados a um sistema externo e continuar sua execução enquanto aguarda a resposta.
UMA INTRODUÇÃO AO HTTP
7. Como cliente e servidor se comunicam?
Uma das tecnologias utilizadas nessa comunicação é o HTTP — Hypertext Transfer Protocol.
De forma simplificada, podemos imaginar:
CLIENTE
│
│ Requisição HTTP
▼
SERVIDOR
│
│ Resposta HTTP
▼
CLIENTE
Para obter informações, utilizaremos principalmente o método HTTP:
GET
O método GET indica, de maneira simplificada:
Solicite a obtenção de um recurso.
Por exemplo:
GET /pokemon/pikachu
Estamos solicitando ao servidor o recurso associado ao Pokémon pikachu.
Uma URL real da PokéAPI será utilizada posteriormente:
https://pokeapi.co/api/v2/pokemon/pikachu
Nesse momento, ainda não precisamos escrever código para fazer essa requisição.
Primeiro precisamos compreender o modelo:
JavaScript
↓
Requisição HTTP GET
↓
Servidor
↓
Resposta
↓
JavaScript
O conteúdo da resposta poderá ser transformado em dados que o programa consiga utilizar.
POR QUE O TEMPO É UM PROBLEMA?
8. E se o servidor demorar?
Considere esta situação:
Aplicação
↓
"Quero os dados do Pikachu"
↓
Servidor
↓
Processando...
↓
Resposta
Quanto tempo o servidor levará?
Não podemos saber antecipadamente.
Pode ser:
50 milissegundos
ou:
500 milissegundos
ou:
3 segundos
ou:
10 segundos
Também pode ocorrer uma falha.
Por exemplo:
Internet indisponível
Servidor indisponível
URL incorreta
Recurso inexistente
Erro no servidor
Portanto, uma aplicação Web precisa estar preparada para situações em que:
A resposta ainda não chegou.
E também para situações em que:
A resposta nunca chegará corretamente.
Esse é um dos motivos pelos quais a programação assíncrona é tão importante no desenvolvimento Web.
RELACIONANDO O EXPERIMENTO COM UMA API
9. Do setTimeout() para uma API
No experimento anterior:
setTimeout(() => {
console.log('Operação concluída');
}, 3000);
simulamos uma operação que termina posteriormente.
Em uma aplicação real, podemos ter:
JavaScript
↓
Solicitação HTTP
↓
Servidor
↓
Processamento
↓
Resposta
A situação é conceitualmente semelhante:
Operação iniciada
↓
Resultado ainda não disponível
↓
Outras atividades podem continuar
↓
Resultado chega posteriormente
↓
Programa processa o resultado
A diferença é que, no caso da API, estamos realmente realizando uma comunicação pela rede.
Na próxima etapa da formação aprenderemos como representar esse resultado futuro utilizando Promises.
ATIVIDADE PRÁTICA
10. Atividade — Observe, preveja e explique
Execute o código:
console.log('A');
setTimeout(() => {
console.log('B');
}, 3000);
console.log('C');
setTimeout(() => {
console.log('D');
}, 1000);
console.log('E');
Questão 1
Qual será a ordem das mensagens exibidas no console?
Questão 2
Por que C aparece antes de B?
Questão 3
Por que E aparece antes de B?
Questão 4
Por que D aparece antes de B?
Questão 5
O que aconteceria se o valor 3000 fosse alterado para 5000?
Questão 6
O que aconteceria se o valor 1000 fosse alterado para 5000?
Questão 7
O seguinte código executa a função imediatamente?
setTimeout(() => {
console.log('Teste');
}, 0);
Explique.
Questão 8
Qual é a relação entre esse experimento e uma aplicação que precisa consultar um servidor?
Questão 9
Explique, com suas próprias palavras, o que significa dizer que uma operação é assíncrona.
DESAFIO DE RACIOCÍNIO
11. Situação-problema
Imagine uma aplicação que precisa consultar um servidor:
Usuário
↓
Clica em "Buscar"
↓
JavaScript solicita os dados
↓
Servidor processa a solicitação
↓
Servidor responde
↓
JavaScript apresenta os dados
Agora imagine que o servidor demore três segundos para responder.
Responda:
- O usuário precisa necessariamente esperar três segundos sem que nada mais aconteça na aplicação?
- O que poderia acontecer com a experiência do usuário se toda a aplicação ficasse bloqueada durante a espera?
- Como o programa poderia saber que a operação terminou?
- Como o programa poderia lidar com uma situação em que a operação falhou?
Essas perguntas serão respondidas progressivamente nas próximas aulas.
RESUMO DA AULA
12. O que aprendemos?
Nesta aula, vimos que:
- Uma aplicação Web pode precisar de dados que não estão disponíveis localmente.
- Esses dados podem estar em um servidor.
- O navegador pode enviar uma requisição ao servidor.
- O servidor pode precisar de tempo para processar a solicitação.
- A resposta pode chegar posteriormente.
- Uma operação pode ser assíncrona.
setTimeout()pode ser utilizado para demonstrar didaticamente uma operação que será executada posteriormente.setTimeout(..., 0)não significa execução imediata.- A programação assíncrona é fundamental para aplicações Web que dependem de operações externas.
- HTTP permite a comunicação entre cliente e servidor.
- O método
GETé utilizado para solicitar a obtenção de recursos. - A próxima etapa será compreender como o JavaScript representa o resultado futuro de uma operação assíncrona.
O modelo conceitual desta aula pode ser resumido assim:
APLICAÇÃO WEB
↓
DADO EXTERNO
↓
SERVIDOR
↓
REQUISIÇÃO
↓
TEMPO DE ESPERA
↓
RESPOSTA POSTERIOR
↓
PROGRAMAÇÃO ASSÍNCRONA
Na próxima aula, estudaremos uma primeira maneira de representar a conclusão de uma operação assíncrona: os callbacks. Em seguida, veremos por que as Promises oferecem uma solução mais estruturada.
EXERCÍCIO PARA CASA
Conteúdo para WordPress
13. Exercício de fixação
Crie um arquivo script.js contendo o código:
console.log('Início da aplicação');
setTimeout(() => {
console.log('Resposta recebida do servidor');
}, 4000);
console.log('Aplicação continua executando');
setTimeout(() => {
console.log('Atualização da interface');
}, 1000);
console.log('Fim da execução inicial');
Tarefas
- Execute o programa e registre a ordem das mensagens.
- Explique por que a mensagem
"Aplicação continua executando"aparece antes de"Resposta recebida do servidor". - Explique por que
"Atualização da interface"aparece antes de"Resposta recebida do servidor". - Modifique os tempos dos dois
setTimeout()e observe novamente o resultado. - Crie uma segunda versão utilizando três operações assíncronas.
- Antes de executar, faça uma previsão da ordem das mensagens.
- Compare sua previsão com o resultado obtido.
Desafio
Imagine que o primeiro setTimeout() represente uma consulta a um servidor.
Escreva, em cinco a dez linhas, uma explicação respondendo:
Por que uma aplicação Web precisa estar preparada para receber uma resposta posteriormente?
JavaScript Assíncrono
Como saber quando uma operação terminou?
Na aula anterior, estudamos um problema fundamental da programação Web.
Uma aplicação pode iniciar uma operação cujo resultado não estará disponível imediatamente.
Por exemplo:
JavaScript
↓
Solicitação
↓
Servidor
↓
Processamento
↓
Resposta
Não sabemos exatamente quando a resposta chegará.
Ela pode chegar rapidamente ou demorar alguns segundos.
Também pode ocorrer uma falha.
Portanto, surge uma nova pergunta:
Como o programa pode saber que uma operação assíncrona terminou?
E, depois:
Como o programa pode receber o resultado dessa operação?
E ainda:
Como o programa pode saber que a operação falhou?
Na Aula 1 utilizamos setTimeout() para visualizar o problema:
console.log('Início');
setTimeout(() => {
console.log('Operação concluída');
}, 3000);
console.log('Fim');
O resultado foi:
Início
Fim
Operação concluída
A função passada para setTimeout() foi executada posteriormente.
Essa função é um exemplo de callback.
Nesta aula, vamos estudar primeiro os callbacks e, em seguida, conhecer uma abstração mais estruturada para representar operações assíncronas: a Promise.
Objetivos
Ao final desta aula, você deverá ser capaz de:
- Explicar o conceito de callback;
- Utilizar uma função como callback;
- Identificar callbacks utilizados em operações assíncronas;
- Compreender o problema de encadear muitas operações por callbacks;
- Explicar o conceito de Promise;
- Identificar os estados
pending,fulfillederejected; - Utilizar
resolve()ereject(); - Consumir uma Promise com
.then(); - Tratar uma rejeição com
.catch(); - Compreender a ideia de encadeamento de Promises.
CALLBACK: UMA FUNÇÃO QUE SERÁ EXECUTADA DEPOIS
1. Relembrando as funções
Uma função pode ser armazenada em uma variável:
const mensagem = function() {
console.log('Olá!');
};
Também podemos utilizar uma arrow function:
const mensagem = () => {
console.log('Olá!');
};
Uma função também pode ser passada como argumento para outra função.
Observe:
function executar(funcao) {
funcao();
}
executar(() => {
console.log('Olá!');
});
Nesse exemplo, a função executar() recebe outra função como argumento.
Essa função recebida é chamada de callback.
Definição
Callback é uma função fornecida a outra função ou mecanismo para que seja executada posteriormente ou em resposta a determinado evento ou operação.
O conceito de callback, portanto, não é exclusivo da programação assíncrona.
Uma callback pode ser executada imediatamente:
function executar(funcao) {
funcao();
}
Ou posteriormente:
function executarDepois(funcao) {
setTimeout(funcao, 2000);
}
O importante é compreender que:
Uma callback é uma função que será utilizada por outro código em determinado momento.
CALLBACK COM setTimeout()
2. Utilizando uma callback em uma operação posterior
Considere:
function executarDepois(callback) {
setTimeout(() => {
callback();
}, 2000);
}
Agora podemos utilizar a função:
executarDepois(() => {
console.log('Operação concluída');
});
O fluxo pode ser representado assim:
executarDepois()
↓
Recebe uma callback
↓
Agenda uma operação
↓
2 segundos
↓
Executa a callback
↓
"Operação concluída"
A callback permite estabelecer uma regra:
Quando a operação terminar, execute esta função.
Essa ideia é extremamente importante.
Podemos imaginar uma operação qualquer:
Iniciar operação
↓
esperar
↓
Operação terminou
↓
Executar callback
Em uma aplicação real, a operação poderia ser:
Consultar servidor
↓
Receber resposta
↓
Executar callback
A callback seria o mecanismo utilizado para continuar o processamento.
CALLBACK TAMBÉM PODE RECEBER DADOS
3. A operação pode produzir um resultado
Até agora, nossa callback não recebeu nenhum dado.
Mas uma operação assíncrona normalmente produz algum resultado.
Podemos fazer:
function buscarDados(callback) {
setTimeout(() => {
const dados = {
nome: 'Pikachu',
id: 25
};
callback(dados);
}, 2000);
}
Agora podemos utilizar:
buscarDados((dados) => {
console.log(dados);
});
Depois de aproximadamente dois segundos, teremos:
{
nome: 'Pikachu',
id: 25
}
Podemos acessar propriedades específicas:
buscarDados((dados) => {
console.log(dados.nome);
console.log(dados.id);
});
Resultado:
Pikachu
25
Temos agora um modelo muito importante:
Operação
↓
aguarda
↓
produz resultado
↓
callback(resultado)
O resultado da operação é entregue à função que foi preparada para recebê-lo.
E SE A OPERAÇÃO FALHAR?
4. Operações também podem apresentar erros
Imagine:
JavaScript
↓
Solicitação
↓
Servidor
↓
ERRO
Uma operação assíncrona pode:
- Ser concluída com sucesso;
- Falhar;
- Retornar um resultado inválido;
- Não encontrar o recurso solicitado;
- Encontrar um problema de comunicação.
Nos callbacks, uma forma tradicional de representar essa situação é utilizar duas funções:
function buscarDados(sucesso, erro) {
// operação assíncrona
}
Por exemplo:
function buscarDados(sucesso, erro) {
setTimeout(() => {
const funcionou = true;
if (funcionou) {
sucesso({
nome: 'Pikachu',
id: 25
});
} else {
erro('Não foi possível obter os dados.');
}
}, 2000);
}
Uso:
buscarDados(
(dados) => {
console.log('Sucesso:', dados);
},
(mensagem) => {
console.error('Erro:', mensagem);
}
);
Temos agora dois caminhos:
Operação
│
┌──────┴──────┐
│ │
sucesso erro
│ │
▼ ▼
dados mensagem
Esse modelo funciona.
Entretanto, à medida que a aplicação cresce, o código pode ficar difícil de organizar.
É nesse contexto que surge uma solução mais estruturada:
Promise.
O QUE É UMA PROMISE?
5. Representando um resultado futuro
Uma Promise representa o resultado futuro de uma operação assíncrona.
Em vez de receber imediatamente o resultado da operação, o programa recebe uma representação de algo que:
ainda está sendo processado e produzirá um resultado posteriormente.
Podemos representar:
Operação assíncrona
↓
Promise
↓
┌──────┴──────┐
↓ ↓
Sucesso Falha
↓ ↓
Resultado Erro
Uma Promise pode ser criada com:
const promessa = new Promise(
(resolve, reject) => {
// operação assíncrona
}
);
A função fornecida ao construtor recebe dois parâmetros especiais:
resolve
reject
resolve() representa a conclusão bem-sucedida.
reject() representa a falha da operação.
Podemos escrever:
const promessa = new Promise(
(resolve, reject) => {
setTimeout(() => {
resolve('Operação concluída');
}, 2000);
}
);
A Promise foi criada, mas ainda não sabemos seu resultado imediatamente.
Ela será resolvida depois de aproximadamente dois segundos.
OS ESTADOS DE UMA PROMISE
6. Uma Promise possui estados
Enquanto uma operação assíncrona está em andamento, sua Promise encontra-se inicialmente em estado:
pending
Podemos traduzir como:
pendente
Quando a operação termina com sucesso:
fulfilled
Ou seja:
cumprida/resolvida com sucesso
Quando a operação termina com falha:
rejected
Ou seja:
rejeitada
O ciclo pode ser representado assim:
PENDING
│
┌────────┴────────┐
│ │
resolve() reject()
│ │
▼ ▼
FULFILLED REJECTED
Uma Promise começa como:
PENDING
e posteriormente termina em um dos dois estados:
FULFILLED
ou:
REJECTED
Ela não volta para pending depois de ter sido estabelecida como concluída ou rejeitada.
resolve() E reject()
7. Como uma Promise termina?
Considere:
const promessa = new Promise(
(resolve, reject) => {
resolve('Tudo certo!');
}
);
A chamada:
resolve('Tudo certo!');
indica:
A operação terminou com sucesso e seu resultado é
"Tudo certo!".
Agora:
const promessa = new Promise(
(resolve, reject) => {
reject('Algo deu errado!');
}
);
A chamada:
reject('Algo deu errado!');
indica:
A operação terminou com falha.
Podemos pensar:
resolve(valor)
↓
Promise fulfilled
↓
valor disponível
reject(erro)
↓
Promise rejected
↓
erro disponível
CONSUMINDO UMA PROMISE COM .then()
8. Como receber o resultado?
Criar uma Promise não significa que o resultado será automaticamente exibido.
Precisamos informar o que fazer quando ela for cumprida.
Para isso, utilizamos:
.then()
Exemplo:
const promessa = new Promise(
(resolve, reject) => {
setTimeout(() => {
resolve('Dados carregados!');
}, 2000);
}
);
Agora:
promessa.then((resultado) => {
console.log(resultado);
});
O fluxo será:
Promise criada
↓
PENDING
↓
2 segundos
↓
resolve()
↓
FULFILLED
↓
.then()
↓
console.log()
A mensagem:
Dados carregados!
será exibida quando a Promise for cumprida.
Portanto:
.then()define o que será executado quando a Promise for cumprida.
TRATANDO ERROS COM .catch()
9. E quando a Promise for rejeitada?
Considere:
const promessa = new Promise(
(resolve, reject) => {
setTimeout(() => {
reject('Falha ao carregar os dados.');
}, 2000);
}
);
Podemos tratar a rejeição utilizando:
.catch()
Exemplo:
promessa
.then((resultado) => {
console.log(resultado);
})
.catch((erro) => {
console.error(erro);
});
O fluxo pode ser representado assim:
Promise
│
┌─────┴─────┐
│ │
sucesso falha
│ │
▼ ▼
.then() .catch()
│ │
▼ ▼
resultado erro
Assim:
.then()trata o sucesso;.catch()trata a rejeição.
Esse padrão será muito importante quando começarmos a trabalhar com APIs.
UMA PROMISE QUE RETORNA DADOS
10. Simulando um servidor
Vamos criar uma função que simula uma consulta a um servidor:
function consultarServidor() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
nome: 'Pikachu',
id: 25,
tipo: 'Elétrico'
});
}, 2000);
});
}
A função não retorna diretamente o objeto:
{
nome: 'Pikachu',
id: 25,
tipo: 'Elétrico'
}
Ela retorna uma:
Promise
Essa Promise representará o resultado futuro da consulta.
Podemos utilizá-la:
consultarServidor()
.then((dados) => {
console.log(dados);
});
Depois:
consultarServidor()
.then((dados) => {
console.log(dados.nome);
console.log(dados.id);
console.log(dados.tipo);
});
O modelo é:
consultarServidor()
↓
Promise
↓
aguarda
↓
resolve()
↓
dados
↓
.then()
Agora temos uma estrutura muito mais próxima do que encontraremos posteriormente em uma API.
SIMULANDO SUCESSO E FALHA
11. Uma operação pode funcionar ou falhar
Vamos criar uma versão que pode produzir dois resultados:
function consultarServidor() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const sucesso = true;
if (sucesso) {
resolve({
nome: 'Pikachu',
id: 25
});
} else {
reject(
new Error(
'Servidor indisponível.'
)
);
}
}, 2000);
});
}
Agora podemos consumir:
consultarServidor()
.then((dados) => {
console.log('Dados:', dados);
})
.catch((erro) => {
console.error(
'Erro:',
erro.message
);
});
Se:
const sucesso = true;
o fluxo será:
PENDING
↓
resolve()
↓
FULFILLED
↓
then()
Se:
const sucesso = false;
o fluxo será:
PENDING
↓
reject()
↓
REJECTED
↓
catch()
ENCADEANDO PROMISES
12. Uma Promise pode produzir outra Promise
Uma característica importante das Promises é a possibilidade de encadear operações.
Por exemplo:
consultarServidor()
.then((dados) => {
console.log('Primeira operação:', dados);
return outraOperacao();
})
.then((resultado) => {
console.log('Segunda operação:', resultado);
})
.catch((erro) => {
console.error('Erro:', erro);
});
A ideia é:
Operação 1
↓
Promise
↓
.then()
↓
Operação 2
↓
Promise
↓
.then()
↓
Resultado final
Esse mecanismo permite organizar sequências de operações assíncronas.
Entretanto, quando muitas operações são encadeadas dessa maneira, o código pode se tornar mais difícil de ler.
É justamente por isso que, na próxima aula, estudaremos uma forma mais legível de escrever esse tipo de código:
async
await
CALLBACK × PROMISE
13. Comparando os dois modelos
Callback
consultarServidor((dados) => {
console.log(dados);
});
Promise
consultarServidor()
.then((dados) => {
console.log(dados);
});
A diferença conceitual é importante.
No modelo de callback:
Operação
↓
Recebe função
↓
Executa função posteriormente
No modelo de Promise:
Operação
↓
Retorna Promise
↓
Promise representa o resultado futuro
↓
.then() trata o sucesso
↓
.catch() trata a rejeição
A Promise fornece uma estrutura mais padronizada para representar o estado e o resultado de operações assíncronas.
ATIVIDADE PRÁTICA
14. Exercício — Simulando uma consulta a um servidor
Crie a função:
function consultarServidor() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const sucesso = true;
if (sucesso) {
resolve({
nome: 'Pikachu',
id: 25,
tipo: 'Elétrico'
});
} else {
reject(
new Error(
'Não foi possível consultar o servidor.'
)
);
}
}, 2000);
});
}
Depois execute:
consultarServidor()
.then((dados) => {
console.log('Nome:', dados.nome);
console.log('ID:', dados.id);
console.log('Tipo:', dados.tipo);
})
.catch((erro) => {
console.error(
'Erro:',
erro.message
);
});
Tarefas
1. Explique por que consultarServidor() retorna uma Promise.
2. Explique o que significa resolve().
3. Explique o que significa reject().
4. Explique quando .then() é executado.
5. Explique quando .catch() é executado.
6. Altere:
const sucesso = true;
para:
const sucesso = false;
e observe o resultado.
7. Modifique os dados retornados pela Promise para representar outro Pokémon.
8. Acrescente uma propriedade:
altura
e apresente seu valor no console.
DESAFIO DE RACIOCÍNIO
15. Desafio — O que realmente está acontecendo?
Considere:
function buscarPokemon() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
nome: 'Pikachu',
id: 25
});
}, 3000);
});
}
const resultado = buscarPokemon();
console.log(resultado);
Perguntas
1. A variável resultado contém imediatamente o objeto:
{
nome: 'Pikachu',
id: 25
}
ou contém outra coisa?
2. Por que o objeto ainda não está disponível imediatamente?
3. Qual é o papel da Promise nesse processo?
4. Como podemos acessar o objeto quando a operação terminar?
5. Qual método podemos utilizar para tratar uma eventual rejeição?
RESUMO DA AULA
16. O que aprendemos?
Nesta aula, aprendemos que:
- Uma callback é uma função passada para outro código para ser executada em determinado momento.
- Uma callback não é necessariamente assíncrona.
- Callbacks podem receber resultados produzidos por operações.
- Operações assíncronas também podem falhar.
- Promises representam o resultado futuro de operações assíncronas.
- Uma Promise possui inicialmente o estado
pending. - Uma Promise pode terminar como
fulfilled. - Uma Promise pode terminar como
rejected. resolve()indica conclusão bem-sucedida.reject()indica falha..then()permite tratar o resultado de uma Promise cumprida..catch()permite tratar uma rejeição.- Promises podem ser encadeadas.
- Promise fornece uma estrutura mais organizada para representar operações assíncronas.
async/await, estudado na próxima aula, trabalha sobre Promises.
O modelo conceitual desta aula é:
OPERAÇÃO ASSÍNCRONA
↓
PROMISE
↓
PENDING
↓
┌────┴────┐
↓ ↓
resolve() reject()
↓ ↓
FULFILLED REJECTED
↓ ↓
.then() .catch()
A próxima etapa será:
Promise
↓
async
↓
await
↓
try/catch
↓
fetch()
↓
API
↓
JSON
EXERCÍCIO PARA CASA
17. Exercício para casa — Cadastro assíncrono
Crie uma função chamada:
carregarUsuario()
Essa função deverá retornar uma Promise.
Após dois segundos, ela deverá ser resolvida com o seguinte objeto:
{
nome: 'Ana',
idade: 25,
profissao: 'Desenvolvedora Web'
}
Requisitos
1. A função deve retornar uma Promise.
2. Utilize setTimeout() para simular o tempo de resposta do servidor.
3. Utilize resolve() para entregar o objeto.
4. Utilize .then() para receber os dados.
5. Utilize .catch() para tratar uma eventual rejeição.
6. Apresente no console:
Nome:
Idade:
Profissão:
Desafio
Modifique a função para que exista uma possibilidade de falha.
Por exemplo:
const sucesso = Math.random() > 0.3;
Se sucesso for verdadeiro:
resolve(dados);
Caso contrário:
reject(
new Error('Falha ao consultar o servidor.')
);
Execute várias vezes e observe que a operação pode terminar por dois caminhos diferentes:
SUCESSO → resolve() → then()
FALHA → reject() → catch()
Questão final
Escreva uma explicação de cinco a dez linhas respondendo:
Por que uma Promise é mais adequada do que simplesmente retornar o resultado de uma operação que ainda não terminou?
DA PROMISE PARA ASYNC/AWAIT
async/await, fetch() e APIs
Na aula anterior, aprendemos que uma Promise representa o resultado futuro de uma operação.
Utilizamos:
consultarServidor()
.then((dados) => {
console.log(dados);
})
.catch((erro) => {
console.error(erro);
});
Esse código funciona.
Porém, quando temos várias operações assíncronas, o encadeamento de .then() pode deixar o código mais difícil de acompanhar.
JavaScript oferece uma sintaxe mais próxima da leitura sequencial tradicional:
async
await
Essas palavras-chave não eliminam as Promises.
Pelo contrário:
async/awaité uma forma de trabalhar com Promises utilizando uma sintaxe mais legível.
A partir desta aula, vamos avançar do servidor simulado para uma comunicação real com uma API.
O fluxo será:
Promise
↓
async / await
↓
try / catch
↓
HTTP
↓
API
↓
fetch()
↓
Response
↓
JSON
↓
Objeto JavaScript
Objetivos
Ao final desta aula, você deverá ser capaz de:
- Explicar a finalidade de
async; - Utilizar
awaitcom uma Promise; - Explicar a relação entre
async/awaite Promise; - Utilizar
try/catchpara tratar erros em código assíncrono; - Compreender o modelo básico de comunicação cliente-servidor;
- Identificar uma requisição HTTP
GET; - Compreender o conceito de API e endpoint;
- Explicar o que é JSON;
- Utilizar
fetch(); - Compreender o objeto
Response; - Verificar
response.ok; - Utilizar
response.json(); - Fazer uma primeira requisição real à PokéAPI.
O QUE ASYNC SIGNIFICA?
1. Criando uma função assíncrona
Na aula anterior, tínhamos uma função que retornava uma Promise:
function consultarServidor() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
nome: 'Pikachu',
id: 25
});
}, 2000);
});
}
Podíamos utilizar:
consultarServidor()
.then((dados) => {
console.log(dados);
});
Agora podemos criar uma função utilizando async:
async function iniciar() {
console.log('Iniciando');
}
A palavra:
async
indica que estamos declarando uma função assíncrona.
Uma característica fundamental é:
Uma função declarada com
asyncsempre retorna uma Promise.
Observe:
async function iniciar() {
return 'Olá';
}
Embora tenhamos escrito:
return 'Olá';
o resultado da função é uma Promise.
Podemos verificar:
const resultado = iniciar();
console.log(resultado);
O console apresentará uma Promise.
Podemos consumi-la:
iniciar()
.then((mensagem) => {
console.log(mensagem);
});
O QUE AWAIT FAZ?
Conteúdo para WordPress
2. Aguardando uma Promise
Considere novamente:
consultarServidor()
.then((dados) => {
console.log(dados);
});
Podemos escrever:
async function iniciar() {
const dados =
await consultarServidor();
console.log(dados);
}
E depois:
iniciar();
O código ficou mais parecido com uma sequência tradicional:
iniciar()
↓
consultarServidor()
↓
aguardar resultado
↓
dados
↓
console.log()
A palavra:
await
indica que a função assíncrona deve aguardar a resolução da Promise antes de continuar sua execução naquele ponto.
Portanto:
const dados =
await consultarServidor();
significa, conceitualmente:
Execute a operação e, quando a Promise produzir seu resultado, atribua esse resultado à variável
dadose continue a execução da função.
Atenção
await não bloqueia o navegador inteiro.
Ele suspende a continuação daquela função async enquanto a Promise está pendente.
A interface do navegador continua podendo responder a outras atividades.
COMPARANDO .THEN() E AWAIT
3. Duas formas de consumir uma Promise
Podemos utilizar .then():
consultarServidor()
.then((dados) => {
console.log(dados);
});
Ou await:
async function iniciar() {
const dados =
await consultarServidor();
console.log(dados);
}
iniciar();
As duas formas trabalham com a mesma ideia:
Operação assíncrona
↓
Promise
↓
Resultado
A diferença está principalmente na forma como escrevemos a continuação da operação.
Com .then()
Promise
↓
.then()
↓
resultado
Com await
Promise
↓
await
↓
resultado
Uma função async pode utilizar await para escrever uma sequência de operações assíncronas de maneira mais próxima da estrutura tradicional do programa.
TRATANDO ERROS COM TRY/CATCH
4. E se a operação falhar?
Na aula anterior, utilizamos:
.catch((erro) => {
console.error(erro);
});
Com async/await, podemos utilizar:
try
catch
Exemplo:
async function iniciar() {
try {
const dados =
await consultarServidor();
console.log(dados);
} catch (erro) {
console.error(erro);
}
}
O fluxo será:
Promise
│
┌─────┴─────┐
│ │
sucesso falha
│ │
▼ ▼
await catch
│ │
▼ ▼
dados erro
Podemos comparar:
Promise com .then() e .catch()
consultarServidor()
.then((dados) => {
console.log(dados);
})
.catch((erro) => {
console.error(erro);
});
async/await com try/catch
async function iniciar() {
try {
const dados =
await consultarServidor();
console.log(dados);
} catch (erro) {
console.error(erro);
}
}
Os dois modelos trabalham com Promises.
PRIMEIRO CONTATO COM A INTERNET
5. Quando os dados estão fora da aplicação
Até agora, simulamos um servidor com:
setTimeout()
Agora vamos trabalhar com um servidor real.
Imagine uma aplicação que precisa obter informações sobre um Pokémon.
Essas informações não estão necessariamente no computador do usuário.
Elas estão armazenadas em um servidor.
O modelo é:
Navegador
│
│ requisição
▼
Servidor
│
│ resposta
▼
Navegador
O navegador é o cliente.
O servidor recebe a solicitação, processa a requisição e devolve uma resposta.
Em uma aplicação Web, essa comunicação normalmente utiliza protocolos da Internet, especialmente o HTTP.
HTTP E O MÉTODO GET
6. Uma requisição HTTP
Uma requisição HTTP pode solicitar determinado recurso.
Por exemplo:
GET /pokemon/pikachu
O método:
GET
indica que queremos obter dados.
Em uma aplicação Web, podemos pensar:
Cliente
│
│ GET
│
▼
Servidor
│
│ dados
▼
Cliente
Outros métodos HTTP existem, como:
GET
POST
PUT
PATCH
DELETE
Nesta aula, nosso foco será o:
GET
porque estamos inicialmente consultando dados.
API E ENDPOINT
7. O que é uma API?
Uma API — Application Programming Interface — fornece uma forma padronizada para que diferentes sistemas possam se comunicar.
No nosso projeto, JavaScript será o cliente que solicitará dados de uma API.
A PokéAPI disponibiliza informações sobre Pokémon.
Um endereço como:
https://pokeapi.co/api/v2/pokemon/pikachu
é um endpoint.
Podemos representá-lo assim:
https://pokeapi.co
│
└── /api/v2/pokemon/pikachu
│
└── recurso solicitado
A aplicação poderá enviar uma requisição para esse endpoint e receber os dados correspondentes.
O fluxo será:
JavaScript
↓
Endpoint
↓
PokéAPI
↓
Resposta HTTP
↓
Dados
JSON
8. Como os dados são transportados?
Uma API pode devolver dados em diferentes formatos.
Um formato muito comum em aplicações Web é o JSON — JavaScript Object Notation.
Um exemplo:
{
"name": "pikachu",
"id": 25,
"weight": 60
}
O JSON possui uma estrutura semelhante aos objetos JavaScript.
Podemos ter:
{
"name": "pikachu",
"types": [
{
"type": {
"name": "electric"
}
}
]
}
No JavaScript, depois que os dados forem convertidos para um objeto, poderemos acessar:
dados.name
e:
dados.types[0].type.name
Temos, portanto:
JSON recebido
↓
Conversão
↓
Objeto JavaScript
↓
Propriedades
↓
Aplicação
PRIMEIRA REQUISIÇÃO COM FETCH()
9. Utilizando fetch()
Agora estamos preparados para fazer uma requisição real.
O JavaScript fornece a função:
fetch()
Podemos escrever:
const resposta =
await fetch(
'https://pokeapi.co/api/v2/pokemon/pikachu'
);
Observe a sequência:
fetch()
↓
Promise
↓
await
↓
Response
A variável:
resposta
não contém ainda diretamente o Pokémon.
Ela contém um objeto:
Response
Esse objeto representa a resposta HTTP recebida.
VERIFICANDO A RESPOSTA HTTP
10. response.ok
Depois de receber uma resposta, precisamos verificar se a requisição foi considerada bem-sucedida.
Podemos utilizar:
resposta.ok
Exemplo:
const resposta =
await fetch(
'https://pokeapi.co/api/v2/pokemon/pikachu'
);
console.log(resposta.ok);
Em uma resposta HTTP considerada bem-sucedida, teremos:
true
Podemos criar uma validação:
if (!resposta.ok) {
throw new Error(
`Erro HTTP: ${resposta.status}`
);
}
O código completo:
const resposta =
await fetch(
'https://pokeapi.co/api/v2/pokemon/pikachu'
);
if (!resposta.ok) {
throw new Error(
`Erro HTTP: ${resposta.status}`
);
}
Agora temos uma regra:
Se a resposta HTTP não for considerada bem-sucedida, lançamos uma exceção.
O SEGUNDO AWAIT
11. response.json()
Temos agora:
const resposta =
await fetch(url);
Mas ainda precisamos obter os dados JSON.
Utilizamos:
resposta.json()
Como essa operação também é assíncrona, utilizamos await:
const dados =
await resposta.json();
Temos então duas etapas:
const resposta =
await fetch(url);
const dados =
await resposta.json();
O fluxo completo é:
URL
↓
fetch()
↓
Promise
↓
Response
↓
response.json()
↓
Promise
↓
Objeto JavaScript
Essa sequência é fundamental.
Primeiro await
await fetch(url)
obtém a resposta HTTP.
Segundo await
await resposta.json()
processa o corpo da resposta como JSON.
PRIMEIRO PROGRAMA COMPLETO
12. Consultando o Pikachu
Podemos reunir tudo:
async function buscarPokemon() {
const resposta =
await fetch(
'https://pokeapi.co/api/v2/pokemon/pikachu'
);
if (!resposta.ok) {
throw new Error(
`Erro HTTP: ${resposta.status}`
);
}
const dados =
await resposta.json();
console.log(dados);
}
Agora executamos:
buscarPokemon();
Podemos visualizar informações específicas:
console.log(dados.name);
console.log(dados.id);
console.log(dados.weight);
Porém, existe um problema:
A variável dados foi declarada dentro da função.
Portanto, ela existe naquele escopo.
Podemos então utilizar:
async function buscarPokemon() {
const resposta =
await fetch(
'https://pokeapi.co/api/v2/pokemon/pikachu'
);
if (!resposta.ok) {
throw new Error(
`Erro HTTP: ${resposta.status}`
);
}
const dados =
await resposta.json();
console.log('Nome:', dados.name);
console.log('ID:', dados.id);
console.log('Peso:', dados.weight);
}
TRATANDO ERROS CORRETAMENTE
13. Utilizando try/catch
Agora podemos tornar a função mais robusta:
async function buscarPokemon() {
try {
const resposta =
await fetch(
'https://pokeapi.co/api/v2/pokemon/pikachu'
);
if (!resposta.ok) {
throw new Error(
`Erro HTTP: ${resposta.status}`
);
}
const dados =
await resposta.json();
console.log('Nome:', dados.name);
console.log('ID:', dados.id);
console.log('Peso:', dados.weight);
} catch (erro) {
console.error(
'Não foi possível carregar o Pokémon:',
erro.message
);
}
}
Agora temos:
buscarPokemon()
│
▼
try
│
┌──────┴──────┐
│ │
sucesso erro
│ │
▼ ▼
dados catch
O catch poderá tratar problemas ocorridos durante o fluxo.
UMA FUNÇÃO REUTILIZÁVEL
14. Transformando a consulta em uma função reutilizável
Em vez de deixar o nome pikachu fixo, podemos receber o Pokémon como parâmetro:
async function buscarPokemon(nome) {
try {
const resposta =
await fetch(
`https://pokeapi.co/api/v2/pokemon/${nome}`
);
if (!resposta.ok) {
throw new Error(
`Pokémon não encontrado: ${nome}`
);
}
const dados =
await resposta.json();
return dados;
} catch (erro) {
console.error(
'Erro:',
erro.message
);
}
}
Podemos utilizar:
buscarPokemon('pikachu');
Ou:
buscarPokemon('charizard');
Ou:
buscarPokemon('bulbasaur');
Mas ainda precisamos de uma forma de receber o resultado.
Como a função é async, ela retorna uma Promise.
Podemos escrever:
buscarPokemon('pikachu')
.then((pokemon) => {
console.log(pokemon);
});
Ou:
async function iniciar() {
const pokemon =
await buscarPokemon('pikachu');
console.log(pokemon);
}
A PRIMEIRA LISTA DE POKÉMON
15. Consultando vários Pokémon
Agora queremos consultar uma lista.
A PokéAPI permite controlar a quantidade de recursos retornados por meio de parâmetros como limit e offset.
Para nosso primeiro exemplo, utilizaremos:
https://pokeapi.co/api/v2/pokemon?limit=20
Podemos fazer:
async function carregarPokemons() {
const resposta =
await fetch(
'https://pokeapi.co/api/v2/pokemon?limit=20'
);
if (!resposta.ok) {
throw new Error(
`Erro HTTP: ${resposta.status}`
);
}
const dados =
await resposta.json();
console.log(dados);
}
Depois:
carregarPokemons();
A estrutura retornada contém informações sobre os resultados.
Podemos acessar:
dados.results
E percorrer:
dados.results.forEach((pokemon) => {
console.log(pokemon.name);
});
O fluxo agora é:
API
↓
JSON
↓
Objeto JavaScript
↓
results
↓
Array
↓
forEach()
↓
Nome dos Pokémon
DA API PARA O DOM
16. API → JavaScript → DOM
Agora vamos utilizar um conhecimento que já fazia parte da formação anterior: manipulação do DOM.
Suponha que tenhamos:
<select id="pokemon"></select>
No JavaScript:
const selectPokemon =
document.querySelector('#pokemon');
Podemos carregar os dados:
async function carregarPokemons() {
const resposta =
await fetch(
'https://pokeapi.co/api/v2/pokemon?limit=20'
);
if (!resposta.ok) {
throw new Error(
`Erro HTTP: ${resposta.status}`
);
}
const dados =
await resposta.json();
dados.results.forEach((pokemon) => {
const option =
document.createElement('option');
option.value =
pokemon.name;
option.textContent =
pokemon.name;
selectPokemon.appendChild(option);
});
}
Agora temos uma cadeia completa:
Servidor
↓
HTTP
↓
fetch()
↓
Response
↓
JSON
↓
Objeto JavaScript
↓
Array
↓
forEach()
↓
DOM
↓
<select>
A aplicação deixou de trabalhar apenas com dados estáticos.
Ela está obtendo dados externos e utilizando-os para construir dinamicamente a interface.
Orientação ao professor
Este é o primeiro momento em que o aluno deve perceber a integração entre os conhecimentos anteriores e o novo conteúdo.
Não reensinar:
querySelector()
createElement()
appendChild()
O material anterior já trabalha esses recursos e estabelece o DOM como base para avançar para consumo de dados e programação assíncrona.
Aqui eles entram apenas como ferramentas.
ATIVIDADE PRÁTICA
17. Exercício — Sua primeira API
Crie uma página contendo:
<button id="btnBuscar">
Buscar Pokémon
</button>
<section id="resultado"></section>
No JavaScript, obtenha os elementos:
const btnBuscar =
document.querySelector('#btnBuscar');
const resultado =
document.querySelector('#resultado');
Crie a função:
async function buscarPokemon() {
// código
}
Ela deverá:
1. Fazer uma requisição para:
https://pokeapi.co/api/v2/pokemon/pikachu
2. Utilizar fetch().
3. Utilizar await.
4. Verificar response.ok.
5. Converter a resposta para JSON.
6. Exibir no console:
Nome
ID
Peso
7. Exibir pelo menos o nome do Pokémon dentro de:
<section id="resultado"></section>
8. Utilizar try/catch.
Desafio
Troque:
pikachu
por:
charizard
Depois:
bulbasaur
Depois:
mewtwo
Observe como a mesma função consegue trabalhar com diferentes recursos.
DESAFIO DE RACIOCÍNIO
18. Desafio — O que existe em cada etapa?
Considere:
const resposta =
await fetch(
'https://pokeapi.co/api/v2/pokemon/pikachu'
);
Questão 1
O que a variável resposta representa?
A) O nome do Pokémon
B) O objeto completo do Pokémon
C) Um objeto Response
D) Uma string JSON
Questão 2
Depois de:
const dados =
await resposta.json();
o que dados representa?
A) Uma nova requisição HTTP
B) Os dados convertidos para estruturas JavaScript
C) O objeto Response
D) Uma URL
Questão 3
Por que existem dois await?
const resposta =
await fetch(url);
const dados =
await resposta.json();
Questão 4
Por que fazemos:
if (!resposta.ok) {
throw new Error(...);
}
Questão 5
Qual é a diferença conceitual entre:
fetch(url)
e:
resposta.json()
RESUMO DA AULA
19. O que aprendemos?
Nesta aula, avançamos de Promises simuladas para comunicação real com uma API.
Aprendemos que:
- Uma função
asyncsempre retorna uma Promise. awaitpermite aguardar o resultado de uma Promise dentro de uma funçãoasync.awaitnão bloqueia o navegador inteiro.try/catchpode ser utilizado para tratar erros em código assíncrono.- HTTP é utilizado para a comunicação entre cliente e servidor na Web.
GETé utilizado para solicitar recursos.- Uma API fornece uma interface para comunicação entre sistemas.
- Um endpoint identifica um recurso ou operação disponibilizado pela API.
- JSON é um formato textual de representação de dados.
fetch()realiza uma requisição e retorna uma Promise.fetch()produz um objetoResponse.response.okpermite verificar se a resposta HTTP foi considerada bem-sucedida.response.json()processa o corpo da resposta como JSON.response.json()também retorna uma Promise.- Os dados convertidos podem ser utilizados como objetos e arrays JavaScript.
- Esses dados podem posteriormente ser utilizados para modificar o DOM.
O modelo central desta aula é:
URL
↓
fetch()
↓
Promise
↓
await
↓
Response
↓
response.json()
↓
Promise
↓
await
↓
Objeto JavaScript
↓
DOM
EXERCÍCIO PARA CASA
20. Exercício para casa — Consumindo uma API
Crie uma página que permita pesquisar um Pokémon pelo nome.
Utilize:
<input
type="text"
id="pokemon"
placeholder="Digite o nome do Pokémon"
>
<button id="btnBuscar">
Buscar
</button>
<section id="resultado"></section>
A aplicação deverá utilizar a PokéAPI.
Requisitos
1. Criar uma função async.
2. Ler o nome digitado pelo usuário.
3. Utilizar fetch().
4. Utilizar await.
5. Verificar response.ok.
6. Utilizar response.json().
7. Utilizar try/catch.
8. Apresentar na página:
Nome
ID
Peso
Tipo
9. Caso o Pokémon não exista, apresentar uma mensagem de erro.
Desafio
Apresente também a imagem oficial do Pokémon.
A informação poderá ser obtida a partir da estrutura retornada pela API:
pokemon.sprites
Procure identificar a estrutura necessária antes de escrever o código.
Questão conceitual
Escreva, com suas próprias palavras, uma resposta para:
Por que precisamos de
awaittanto emfetch(url)quanto emresponse.json()?
De JavaScript para aplicações conectadas
Até aqui, construímos uma progressão:
AULA 1
──────────────────────────────
Por que precisamos de assincronismo?
↓
Operações demoradas
↓
setTimeout()
↓
Tempo de resposta
↓
Execução assíncrona
AULA 2
──────────────────────────────
Como saber quando terminou?
↓
Callback
↓
Callback + resultado
↓
Callback + erro
↓
Promise
↓
pending / fulfilled / rejected
↓
resolve / reject
↓
then / catch
AULA 3
──────────────────────────────
Como escrever Promises de forma mais legível?
↓
async
↓
await
↓
try / catch
↓
HTTP
↓
API
↓
JSON
↓
fetch()
↓
Response
↓
response.json()
↓
Objeto JavaScript
↓
DOM
AULA 4
──────────────────────────────
Projeto Pokédex
↓
Uma requisição
↓
Uma resposta
↓
Um Pokémon
↓
Dois Pokémon
↓
Duas requisições
↓
Promise.all()
↓
Cards
↓
Pokédex Assíncrona
A formação passa, portanto, de uma pergunta abstrata:
“O que acontece quando uma operação demora?”
para uma competência prática:
“Como uma aplicação JavaScript solicita dados a uma API, aguarda a resposta, processa os dados e os utiliza para construir sua interface?”
Esse é o ponto em que o estudo de programação assíncrona deixa de ser apenas sintaxe e passa a representar uma competência real de desenvolvimento Web.
DA PRIMEIRA REQUISIÇÃO À POKÉDEX
Promise.all() e construção da Pokédex
Nas aulas anteriores, estudamos progressivamente como uma aplicação JavaScript pode trabalhar com operações assíncronas.
Na Aula 1, compreendemos o problema do tempo de resposta.
Na Aula 2, estudamos callbacks e Promises.
Na Aula 3, utilizamos:
async
await
fetch()
para realizar uma comunicação real com uma API.
Agora vamos utilizar esses conhecimentos para construir uma aplicação completa.
O projeto será uma Pokédex Assíncrona.
A aplicação deverá permitir selecionar dois Pokémon, consultar seus dados na PokéAPI e apresentar os resultados na página.
A sequência será:
Usuário
↓
Seleciona dois Pokémon
↓
Clica em "Buscar"
↓
JavaScript inicia duas consultas
↓
Promise.all()
↓
Dados dos dois Pokémon
↓
Renderização
↓
Cards
Objetivos
Ao final desta aula, você deverá ser capaz de:
- Compreender o problema de realizar várias requisições independentes;
- Diferenciar execução sequencial de execução concorrente de operações assíncronas;
- Utilizar
Promise.all(); - Interpretar os resultados produzidos por
Promise.all(); - Utilizar
async/awaitcom múltiplas Promises; - Utilizar desestruturação para receber múltiplos resultados;
- Integrar dados de uma API com o DOM;
- Criar componentes visuais a partir de dados recebidos;
- Implementar estados básicos de carregamento;
- Implementar tratamento de erros;
- Integrar esses conceitos em uma aplicação Web funcional.
UMA REQUISIÇÃO FUNCIONA
Antes de trabalhar com duas requisições, precisamos compreender claramente o que já conseguimos fazer.
Considere:
async function buscarPokemon(nome) {
const resposta = await fetch(
`https://pokeapi.co/api/v2/pokemon/${nome}`
);
if (!resposta.ok) {
throw new Error(
`Erro HTTP: ${resposta.status}`
);
}
const dados = await resposta.json();
return dados;
}
A função recebe o nome de um Pokémon.
Por exemplo:
const pokemon = await buscarPokemon('pikachu');
Depois que a operação terminar, pokemon conterá os dados retornados pela API.
Podemos acessar propriedades:
console.log(pokemon.name);
console.log(pokemon.id);
console.log(pokemon.weight);
O fluxo é:
nome
↓
buscarPokemon()
↓
fetch()
↓
Response
↓
response.json()
↓
objeto JavaScript
Portanto, já sabemos consultar um recurso.
Agora surge um novo problema:
E se precisarmos consultar dois recursos independentes?
O PROBLEMA DE DUAS REQUISIÇÕES
Imagine que o usuário tenha escolhido:
Pokémon 1: Pikachu
Pokémon 2: Charizard
Precisamos consultar:
https://pokeapi.co/api/v2/pokemon/pikachu
e:
https://pokeapi.co/api/v2/pokemon/charizard
Uma primeira solução seria:
const pokemon1 =
await buscarPokemon('pikachu');
const pokemon2 =
await buscarPokemon('charizard');
Essa solução funciona.
Mas observe a sequência:
Inicia Pikachu
↓
Aguarda Pikachu
↓
Pikachu termina
↓
Inicia Charizard
↓
Aguarda Charizard
↓
Charizard termina
As duas consultas são independentes.
Para consultar Charizard, não precisamos esperar o resultado de Pikachu.
Então surge uma questão:
Por que iniciar a segunda operação somente depois que a primeira terminou?
PRIMEIRA EXPERIÊNCIA COM DUAS PROMISES
Podemos iniciar as duas operações antes de esperar pelos resultados:
const promessa1 =
buscarPokemon('pikachu');
const promessa2 =
buscarPokemon('charizard');
Agora temos:
promessa1 → consulta do Pikachu
promessa2 → consulta do Charizard
Cada variável contém uma Promise.
Podemos então aguardar cada resultado:
const pokemon1 =
await promessa1;
const pokemon2 =
await promessa2;
O código já permite iniciar as duas operações antes de aguardar seus resultados.
Entretanto, ainda precisamos de uma maneira organizada de dizer:
“Quero aguardar um conjunto de Promises.”
É exatamente esse problema que Promise.all() resolve.
CONHECENDO PROMISE.ALL()
Promise.all() recebe um conjunto de Promises e retorna uma nova Promise.
Exemplo:
const resultados = await Promise.all([
buscarPokemon('pikachu'),
buscarPokemon('charizard')
]);
Quando as duas operações forem concluídas, resultados conterá os dois valores.
Podemos imaginar:
Promise.all()
│
├── buscarPokemon('pikachu')
│
└── buscarPokemon('charizard')
│
▼
dois resultados
O resultado será um array:
[
dadosDoPikachu,
dadosDoCharizard
]
A ordem dos resultados corresponde à ordem das Promises fornecidas.
Portanto:
const resultados = await Promise.all([
buscarPokemon('pikachu'),
buscarPokemon('charizard')
]);
significa:
resultados[0] → Pikachu
resultados[1] → Charizard
Podemos então escrever:
const pokemon1 = resultados[0];
const pokemon2 = resultados[1];
DESESTRUTURAÇÃO DOS RESULTADOS
Podemos escrever o código anterior de maneira mais direta utilizando desestruturação.
Em vez de:
const resultados = await Promise.all([
buscarPokemon('pikachu'),
buscarPokemon('charizard')
]);
const pokemon1 = resultados[0];
const pokemon2 = resultados[1];
podemos escrever:
const [
pokemon1,
pokemon2
] = await Promise.all([
buscarPokemon('pikachu'),
buscarPokemon('charizard')
]);
Agora temos:
pokemon1 → dados do Pikachu
pokemon2 → dados do Charizard
O código completo fica:
async function buscarDoisPokemons() {
const [
pokemon1,
pokemon2
] = await Promise.all([
buscarPokemon('pikachu'),
buscarPokemon('charizard')
]);
console.log(pokemon1);
console.log(pokemon2);
}
Podemos executar:
buscarDoisPokemons();
E SE UMA DAS REQUISIÇÕES FALHAR?
Existe uma característica importante de Promise.all().
Imagine:
const [
pokemon1,
pokemon2
] = await Promise.all([
buscarPokemon('pikachu'),
buscarPokemon('pokemon-que-nao-existe')
]);
Uma das operações falhará.
Nesse caso, a Promise retornada por Promise.all() será rejeitada.
Por isso, precisamos utilizar:
try {
// operações
} catch (erro) {
// tratamento do erro
}
Exemplo:
async function buscarDoisPokemons() {
try {
const [
pokemon1,
pokemon2
] = await Promise.all([
buscarPokemon('pikachu'),
buscarPokemon('pokemon-que-nao-existe')
]);
console.log(pokemon1);
console.log(pokemon2);
} catch (erro) {
console.error(erro);
}
}
O fluxo pode ser representado assim:
Promise.all()
│
┌─────────┴─────────┐
│ │
Consulta 1 Consulta 2
│ │
sucesso erro
│ │
└─────────┬─────────┘
↓
catch
PREPARANDO A INTERFACE
Agora vamos aplicar o conceito ao projeto.
A interface terá dois campos de seleção:
<select id="pokemon1"></select>
<select id="pokemon2"></select>
<button id="btnBuscar">
Buscar Pokémons
</button>
<section id="cards"></section>
O JavaScript poderá localizar esses elementos:
const selectPokemon1 =
document.querySelector('#pokemon1');
const selectPokemon2 =
document.querySelector('#pokemon2');
const btnBuscar =
document.querySelector('#btnBuscar');
const cardsContainer =
document.querySelector('#cards');
O fluxo da aplicação será:
<select> Pokémon 1
+
<select> Pokémon 2
↓
Botão Buscar
↓
renderizarPokemons()
↓
buscarPokemon()
↓
Promise.all()
↓
dados dos dois Pokémon
↓
criarCard()
↓
DOM
O DOM não é o responsável por realizar a consulta.
O DOM fornece a entrada e recebe o resultado.
A comunicação com a API é realizada pelo JavaScript assíncrono.
CARREGANDO OS POKÉMON NOS SELECT
A PokéAPI permite consultar uma lista de Pokémon.
Utilizaremos inicialmente:
https://pokeapi.co/api/v2/pokemon?limit=20
Podemos criar:
async function carregarPokemons() {
const resposta = await fetch(
'https://pokeapi.co/api/v2/pokemon?limit=20'
);
if (!resposta.ok) {
throw new Error(
`Erro HTTP: ${resposta.status}`
);
}
const dados = await resposta.json();
console.log(dados.results);
}
A propriedade:
dados.results
contém um array.
Cada elemento possui informações como:
{
name: 'bulbasaur',
url: 'https://pokeapi.co/api/v2/pokemon/1/'
}
Podemos transformar cada item em uma opção:
const optionsHTML =
dados.results
.map(pokemon => `
<option value="${pokemon.name}">
${pokemon.name}
</option>
`)
.join('');
Depois:
selectPokemon1.innerHTML =
optionsHTML;
selectPokemon2.innerHTML =
optionsHTML;
Assim, os dois campos receberão a mesma lista de Pokémon.
CRIANDO A FUNÇÃO buscarPokemon()
Agora precisamos de uma função que consulte um Pokémon específico.
async function buscarPokemon(nome) {
const resposta = await fetch(
`https://pokeapi.co/api/v2/pokemon/${nome}`
);
if (!resposta.ok) {
throw new Error(
`Pokémon não encontrado: ${nome}`
);
}
const dados = await resposta.json();
return dados;
}
Essa função recebe:
nome
e retorna:
dados
Por exemplo:
const pokemon =
await buscarPokemon('pikachu');
Agora podemos acessar:
pokemon.name
pokemon.id
pokemon.weight
pokemon.types
pokemon.abilities
pokemon.sprites
A função é reutilizável.
Podemos chamar:
await buscarPokemon('pikachu');
ou:
await buscarPokemon('charizard');
ou:
await buscarPokemon('bulbasaur');
AS DUAS CONSULTAS COM PROMISE.ALL()
Agora temos todas as peças necessárias.
Precisamos ler os valores selecionados:
const nome1 =
selectPokemon1.value;
const nome2 =
selectPokemon2.value;
Depois consultar os dois Pokémon:
const [
pokemon1,
pokemon2
] = await Promise.all([
buscarPokemon(nome1),
buscarPokemon(nome2)
]);
O código completo da função será:
async function buscarDoisPokemons() {
const nome1 =
selectPokemon1.value;
const nome2 =
selectPokemon2.value;
const [
pokemon1,
pokemon2
] = await Promise.all([
buscarPokemon(nome1),
buscarPokemon(nome2)
]);
console.log(pokemon1);
console.log(pokemon2);
}
Podemos associar a função ao botão:
btnBuscar.addEventListener(
'click',
buscarDoisPokemons
);
Agora o usuário pode selecionar dois Pokémon e solicitar seus dados.
CRIANDO OS CARDS
Agora os dados já estão disponíveis.
Precisamos apresentá-los na página.
Podemos criar uma função:
function criarCard(pokemon) {
const nome =
pokemon.name;
const id =
pokemon.id;
const peso =
pokemon.weight;
const tipo =
pokemon.types[0].type.name;
const habilidade =
pokemon.abilities[0].ability.name;
const imagem =
pokemon.sprites
.other['official-artwork']
.front_default;
cardsContainer.insertAdjacentHTML(
'beforeend',
`
<article class="card">
<img
src="${imagem}"
alt="${nome}"
>
<h2>${nome}</h2>
<p>ID: ${id}</p>
<p>Tipo: ${tipo}</p>
<p>Peso: ${peso}</p>
<p>
Habilidade: ${habilidade}
</p>
</article>
`
);
}
Agora podemos utilizar:
criarCard(pokemon1);
criarCard(pokemon2);
O fluxo passa a ser:
API
↓
JSON
↓
Objeto Pokémon
↓
criarCard()
↓
HTML
↓
DOM
A aplicação deixa de apenas mostrar dados no console.
Agora os dados recebidos pela API passam a formar parte da interface.
INTEGRANDO A RENDERIZAÇÃO
Agora podemos reunir as etapas.
async function renderizarPokemons() {
try {
cardsContainer.innerHTML = '';
const nome1 =
selectPokemon1.value;
const nome2 =
selectPokemon2.value;
const [
pokemon1,
pokemon2
] = await Promise.all([
buscarPokemon(nome1),
buscarPokemon(nome2)
]);
criarCard(pokemon1);
criarCard(pokemon2);
} catch (erro) {
console.error(erro);
cardsContainer.innerHTML =
'<p>Erro ao carregar os dados.</p>';
}
}
E associamos ao botão:
btnBuscar.addEventListener(
'click',
renderizarPokemons
);
A arquitetura agora é:
CLIQUE
↓
renderizarPokemons()
↓
ler selects
↓
buscarPokemon()
↓
Promise.all()
↓
pokemon1 + pokemon2
↓
criarCard()
↓
DOM
Essa função reúne os conceitos estudados durante as quatro aulas.
ESTADO DE CARREGAMENTO
Existe ainda um problema de interface.
Quando o usuário clicar em:
Buscar Pokémons
as requisições poderão levar algum tempo.
Durante esse período, a aplicação não deve parecer travada.
Podemos informar:
Carregando Pokémon...
Antes da consulta:
cardsContainer.innerHTML =
'<p>Carregando Pokémon...</p>';
Depois que os dados chegarem:
criarCard(pokemon1);
criarCard(pokemon2);
Assim, temos três situações principais:
IDLE
Aplicação pronta
↓
LOADING
Carregando dados
↓
SUCCESS
Dados apresentados
Se ocorrer um erro:
LOADING
↓
ERROR
↓
Mensagem apresentada
Uma aplicação assíncrona precisa considerar não apenas os dados, mas também o estado da interface durante a operação.
TRATAMENTO DE ERRO PARA O USUÁRIO
Erros fazem parte de aplicações que dependem de serviços externos.
Por exemplo:
- O servidor pode estar indisponível;
- A conexão pode falhar;
- O recurso pode não existir;
- A API pode retornar um erro HTTP;
- Um dado inesperado pode provocar um erro durante a renderização.
Por isso, utilizamos:
try {
// operação
} catch (erro) {
// tratamento
}
Na aplicação, podemos apresentar:
cardsContainer.innerHTML =
`
<p>
Não foi possível carregar os dados.
Tente novamente.
</p>
`;
O console pode receber informações técnicas:
console.error(erro);
Assim, temos duas finalidades diferentes:
Usuário
↓
Mensagem simples e compreensível
Desenvolvedor
↓
Informação técnica no console
ATENÇÃO AO USO DE DADOS EXTERNOS
A aplicação recebe informações de uma fonte externa.
Esses dados devem ser tratados como dados, e não automaticamente como HTML confiável.
Por exemplo:
element.textContent = valor;
insere um valor como texto.
Já:
element.innerHTML = valor;
interpreta o conteúdo como HTML.
Em nosso projeto, utilizamos uma API conhecida e controlada para fins didáticos.
Mesmo assim, é importante desenvolver uma regra profissional:
Dados externos não devem ser considerados automaticamente confiáveis para inserção como HTML.
Ao desenvolver aplicações reais, é necessário analisar a origem dos dados e escolher cuidadosamente a técnica de renderização.
CÓDIGO FINAL DA APLICAÇÃO
Depois de construir cada etapa, podemos reunir o código.
'use strict';
const API_URL =
'https://pokeapi.co/api/v2/pokemon';
const selectPokemon1 =
document.querySelector('#pokemon1');
const selectPokemon2 =
document.querySelector('#pokemon2');
const btnBuscar =
document.querySelector('#btnBuscar');
const cardsContainer =
document.querySelector('#cards');
async function fetchJSON(url) {
const response =
await fetch(url);
if (!response.ok) {
throw new Error(
`Erro HTTP: ${response.status}`
);
}
return await response.json();
}
async function carregarPokemons() {
const dados =
await fetchJSON(
`${API_URL}?limit=20`
);
const optionsHTML =
dados.results
.map(pokemon => `
<option value="${pokemon.name}">
${pokemon.name}
</option>
`)
.join('');
selectPokemon1.innerHTML =
optionsHTML;
selectPokemon2.innerHTML =
optionsHTML;
}
async function buscarPokemon(nome) {
return await fetchJSON(
`${API_URL}/${nome}`
);
}
function criarCard(pokemon) {
const nome =
pokemon.name;
const id =
pokemon.id;
const peso =
pokemon.weight;
const tipo =
pokemon.types[0]
.type.name;
const habilidade =
pokemon.abilities[0]
.ability.name;
const imagem =
pokemon.sprites
.other['official-artwork']
.front_default;
cardsContainer.insertAdjacentHTML(
'beforeend',
`
<article class="card">
<img
src="${imagem}"
alt="${nome}"
>
<h2>${nome}</h2>
<p>ID: ${id}</p>
<p>Tipo: ${tipo}</p>
<p>Peso: ${peso}</p>
<p>
Habilidade: ${habilidade}
</p>
</article>
`
);
}
async function renderizarPokemons() {
try {
cardsContainer.innerHTML =
'<p>Carregando Pokémon...</p>';
const nome1 =
selectPokemon1.value;
const nome2 =
selectPokemon2.value;
const [
pokemon1,
pokemon2
] = await Promise.all([
buscarPokemon(nome1),
buscarPokemon(nome2)
]);
cardsContainer.innerHTML = '';
criarCard(pokemon1);
criarCard(pokemon2);
} catch (erro) {
console.error(erro);
cardsContainer.innerHTML =
`
<p>
Não foi possível carregar
os dados dos Pokémon.
</p>
`;
}
}
btnBuscar.addEventListener(
'click',
renderizarPokemons
);
carregarPokemons();
O código final não deve ser entendido como uma sequência de comandos isolados.
Ele representa uma arquitetura:
carregarPokemons()
↓
lista da API
↓
<select>
Usuário
↓
evento click
↓
renderizarPokemons()
↓
Promise.all()
↓
dois objetos Pokémon
↓
criarCard()
↓
DOM
Essa é a arquitetura básica de uma aplicação Web que consome dados de uma API de maneira assíncrona.
EXERCÍCIOS DE CONSOLIDAÇÃO
Exercícios
Exercício 1 — Uma requisição
Modifique a aplicação para apresentar apenas um Pokémon.
Utilize:
buscarPokemon()
e apresente:
- Nome;
- ID;
- Peso;
- Tipo;
- Imagem.
Exercício 2 — Duas requisições sequenciais
Implemente:
const pokemon1 =
await buscarPokemon('pikachu');
const pokemon2 =
await buscarPokemon('charizard');
Observe a estrutura da execução.
Exercício 3 — Duas requisições independentes
Modifique o código para iniciar as duas consultas antes de aguardar os resultados.
Exercício 4 — Promise.all()
Utilize:
const [
pokemon1,
pokemon2
] = await Promise.all([
buscarPokemon('pikachu'),
buscarPokemon('charizard')
]);
Apresente os dois resultados no console.
Exercício 5 — Tratamento de erro
Substitua um dos nomes por:
pokemon-inexistente
Observe o comportamento da aplicação.
Depois implemente uma mensagem amigável para o usuário.
Exercício 6 — Terceiro Pokémon
Modifique a aplicação para trabalhar com três Pokémon.
Utilize:
Promise.all([
buscarPokemon('pikachu'),
buscarPokemon('charizard'),
buscarPokemon('bulbasaur')
]);
Apresente os três cards.
Exercício 7 — Estado de carregamento
Adicione uma mensagem:
Carregando...
enquanto as consultas estiverem sendo realizadas.
Exercício 8 — Transferência de aprendizagem
Substitua a PokéAPI por outra API pública.
A nova aplicação deverá manter a mesma arquitetura:
Evento
↓
Função async
↓
fetch()
↓
Response
↓
JSON
↓
Promise
↓
Promise.all()
↓
Dados
↓
DOM
O domínio deverá ser diferente de Pokémon.
DESAFIO DE RACIOCÍNIO
Considere:
const [
pokemon1,
pokemon2
] = await Promise.all([
buscarPokemon('pikachu'),
buscarPokemon('charizard')
]);
Responda às questões:
1. Quantas Promises estão sendo fornecidas ao Promise.all()?
2. Qual operação corresponde a pokemon1?
3. Qual operação corresponde a pokemon2?
4. O que acontece se a consulta de Pikachu terminar antes da consulta de Charizard?
5. O que acontece se a consulta de Charizard terminar antes da consulta de Pikachu?
6. O que acontece se uma das duas consultas falhar?
7. Por que utilizar Promise.all() nesse caso?
8. Qual é a diferença entre:
await buscarPokemon('pikachu');
e:
await Promise.all([
buscarPokemon('pikachu'),
buscarPokemon('charizard')
]);
Questão final
Explique, com suas próprias palavras:
Por que
Promise.all()é adequado quando temos várias operações assíncronas independentes que precisam ser concluídas antes de continuar?
O QUE APRENDEMOS
Síntese da Aula
Nesta aula, avançamos de uma única requisição para múltiplas requisições assíncronas.
Aprendemos que:
- Uma aplicação pode realizar várias operações assíncronas independentes;
- Duas consultas independentes não precisam ser iniciadas sequencialmente;
Promise.all()permite aguardar um conjunto de Promises;- Os resultados permanecem associados à ordem das Promises fornecidas;
- Se uma Promise rejeitar, o
Promise.all()também será rejeitado; try/catchpode tratar essa falha;- Os resultados de uma API podem ser utilizados para construir elementos da interface;
- Estados de carregamento melhoram a experiência do usuário;
- Erros devem ser tratados tanto tecnicamente quanto visualmente;
- Uma aplicação Web assíncrona combina eventos, funções, Promises,
async/await, APIs, JSON e DOM.
A arquitetura final estudada foi:
EVENTO
↓
FUNÇÃO ASSÍNCRONA
↓
FETCH
↓
RESPONSE
↓
JSON
↓
PROMISE
↓
PROMISE.ALL()
↓
DADOS
↓
RENDERIZAÇÃO
↓
DOM
↓
INTERFACE
A Pokédex é, portanto, mais do que um exercício sobre Pokémon.
Ela representa um modelo de aplicação Web capaz de:
solicitar dados externos, aguardar operações assíncronas, tratar resultados e erros e transformar dados recebidos em uma interface dinâmica.
EXERCÍCIO PARA CASA
Exercício para casa — Uma aplicação com duas consultas
Crie uma aplicação Web que permita ao usuário escolher dois recursos de uma API pública.
A aplicação deverá:
- Possuir dois campos de seleção ou entrada;
- Possuir um botão para iniciar a consulta;
- Utilizar uma função
async; - Utilizar
fetch(); - Utilizar
response.ok; - Utilizar
response.json(); - Utilizar
Promise.all(); - Utilizar
try/catch; - Apresentar uma mensagem de carregamento;
- Apresentar os resultados na página;
- Apresentar uma mensagem caso ocorra erro.
Requisito principal
As duas consultas deverão ser independentes.
A solução deverá utilizar:
Promise.all([
primeiraConsulta,
segundaConsulta
]);
Desafio
Não utilize a PokéAPI.
Escolha outro domínio.
Exemplos:
- Filmes;
- Países;
- Produtos;
- Livros;
- Jogos;
- Astronomia;
- Notícias;
- Dados meteorológicos.
A estrutura da aplicação deverá continuar sendo:
Usuário
↓
Evento
↓
async function
↓
fetch()
↓
Promise.all()
↓
JSON
↓
Dados
↓
DOM
Reflexão final
Escreva um pequeno texto respondendo:
O que muda no desenvolvimento de uma aplicação quando os dados deixam de estar disponíveis localmente e passam a depender de um servidor externo?
REFERÊNCIAS
MDN Web Docs. JavaScript assíncrono — Promises.
MDN Web Docs. Promise.
MDN Web Docs. async function.
WHATWG. Fetch Standard.
PokéAPI. Documentation.
Mozilla Developer Network — MDN Web Docs. JavaScript assíncrono. Documentação oficial.
Mozilla Developer Network — MDN Web Docs. Promises. Documentação oficial.
Mozilla Developer Network — MDN Web Docs. Fetch API. Documentação oficial.
WHATWG. Fetch Standard.
PokéAPI. Documentação oficial da API.
Material anterior da disciplina: Linguagem e Técnica de Programação II — Aula 03, utilizado como pré-requisito para esta unidade. O material anterior estabelece que o domínio de JavaScript, funções, DOM, eventos e formulários constitui a base para avançar para consumo de dados e programação assíncrona. O material da disciplina recomenda a progressão conceitual de programação síncrona para assincronismo, callbacks, Promises, then/catch, async/await e, posteriormente, fetch().
