Objeto: Promise, then, fecth, Objetos JavaScript, formato JSON
Principais termos técnicos abordados: Promise/Promessa, JSON, fetch, await, async, html, JavaScript, arrow function
Requisitos para as instruções funcionarem: editor de texto e navegador instalados
Requisitos para compreensão das instruções: noções bem básicas de HTML e ter implementado receita anterior
Como ler essa receita: instruções, dentro dos passos da receita e que requerem ação sua no computador, estarão escritas em cor azul. Comentários sobre os passos estarão em fonte normal, cor preta. Comandos, código-fonte, termos técnicos ou configuração explícita, estarão com fonte diferenciada.
"Ah, promessa desgraçada! Ah, promessa sem jeito!"
(João Grilo)
Vamos, uma vez mais, mudar as coisas para que continue tudo como está.
Usamos o seguinte código para ir buscar objetos json num site:
async function carregarCervejas(){
try{
let res = await fetch("https://random-data-api.com/api/v2/beers?size=3")
cervejas = await res.json()
carregarDiv(cervejas)
}catch(err){
document.getElementById("cervejasDiv").innerHTML = `Fudeu... ${err}`
}
}
Usamos a palavra await antes da função fetch. E por quê? Vamos entender isso melhor.
A função fetch vai fazer uma requisição a um site, essa requisição vai demorar 1 real e 99 de tempo: uma conexão http vai ser aberta, os dados vão ser enviados, o software no outro lado da conexão vai processar essa requisição etc.
No entanto, malgrado todo esse moído, o fato é que a função fetch retorna quase que imediatamente.
- Mas como ela pode retornar imediatamente, professor, se o resultado final depende do processamento feito numa outra máquina?
Bem, é que ela - a função fetch - retorna imediatamente SEM o resultado da requisição. Ela retorna um objeto que executa o código que faz - entre outras coisas - a requisição. E fica nas entucas esperando o resultado. Por isso a gente botou aquele await. Ele indica que sua função carregarCervejas deve ficar quietinha ali naquele ponto do await, esperando até que o moído que se espera que aconteça efetivamente aconteça.
- Então não tinha como fazer nada sem o await, né, professor? Como seguir com a função se foi retornado um objeto que não tem os resultados da requisição?
Com o tipo de programação que a gente tem em mente, de executar uma coisa atrás da outra, a gente é levado a crer que não tem outro jeito mesmo.
Mas tem.
A questão é que o objeto retornado pelo fetch não tem AINDA os resultados da operação, mas em algum momento futuro ele vai ter: esse objeto é o que chamamos de promessa, ou Promise, no ingrêis. Esse objeto saberá quando a promessa não teve jeito, como a promessa de Chicó lá no Auto da Compadecida, e também quando a promessa teve jeito. Em outras palavras, esse objeto sabe quando a promessa foi cumprida, sabe quando o resultado que a promessa prometeu chegou, e sabe se, eventualmente, aconteceu algum erro.
- Sim, professor, mas a gente precisa processar esse resultado dessa promessa no código da gente, pra transformá-lo em objetos json. E se esse objeto da promessa não foi a gente que implementou, como é que ele vai executar um código da gente quando o resultado chegar?
Bem, esse objeto, ao contrário dos flamenguistas no meu Facebook, ele é um cara legal. Ele vai deixar você lhe dar uma função de callback que você escreveu para ele chamar QUANDO o resultado chegar. Você escreve a função recebendo o resultado como parâmetro, para que esse cara legal - o objeto Promise - possa passar o resultado da operação para você QUANDO o resultado chegar. Assim, você precisa chamar o método then desse objeto, dessa promessa, passando sua função de callback como parâmetro. A promessa vai compreender que deve chamar essa função quando obtiver o resultado que espera.
Vamos ver como isso funciona.
Inicie com o código a seguir, acrescentando uma nova função...
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div><button id="botaoCarregar">Carregando cervejas...</button></div>
<div id="cervejasDiv"></div>
</body>
<script>
let cervejas = []
//cs é um array de cervejas
const carregarDiv = cs => {
const div = document.getElementById("cervejasDiv")
const itensHtml = cs.map( ({name,alcohol}) => `<div>${name} -- ${alcohol}</div>` )
div.innerHTML = `${itensHtml.join("\n")}`
}
async function carregarCervejas(){
try{
let res = await fetch("https://random-data-api.com/api/v2/beers?size=3")
cervejas = await res.json()
carregarDiv(cervejas)
}catch(err){
document.getElementById("cervejasDiv").innerHTML = "Fudeu..."
}
}
function chegouRespostaDoFetch(res){
document.getElementById("cervejasDiv").innerHTML = `Resposta do fetch() -- ${res}`
}
function carregarCervejas2(){
let p = fetch("https://random-data-api.com/api/v2/beers?size=3")
p.then(chegouRespostaDoFetch)
document.getElementById("cervejasDiv").innerHTML = `Fazendo requisição`
}
let botao = document.getElementById("botaoCarregar")
botao.addEventListener("click", () => carregarCervejas2() )
</script>
</html>
Salve, recarregue, acione o botão.
A emoção inicial deve se dar assim:
Isso ocorre pelo que já discutimos.
Note que o clique no botão vai desencadear uma chamada à função carregarCervejas2, que usa uma Promise. Deixei lá no código a função carregarCervejas (sem o 2) apenas para efeitos de comparação, não precisamos chamá-la pois já trabalhamos com ela anteriormente, numa abordagem com o uso de async/await.
Assim, a chamada ao fetch, lá no carregarCervejas2, retorna quase imediatamente, com um objeto Promise que AINDA não tem o resultado da operação como um todo. Referenciamos esse objeto através da varável p.
A chamada p.then(chegouRespostaDoFetch) também retorna quase que imediatamente: estamos apenas pedindo ao objeto p para registrar essa função de callback e invocá-la QUANDO o resultado chegar. A essa altura a requisição deve estar sendo processada remotamente.
Depois disso a próxima linha é a que põe a mensagem "Fazendo requisição" lá no div.
Em seguida, o ciclo gerado pelo evento do clique no botão se encerra, pois a função que foi invocada após o clique concluiu toda sua execução.
MASSSS... em algum canto da memória tem um objeto Promise de tocaia esperando alguma coisa chegar. E quando chegar, esse mesmo objeto foi "instruído", através do método then, a chamar a função de callback chegouRespostaDoFetch, passando o resultado da operação como parâmetro para essa função.
Sendo que a operação que gerou a Promise p foi um fetch(), uma requisição HTTPS. E o resultado dessa operação é um objeto Response, um objeto que tem uma ruma de coisa dentro de si, inclusive o texto dos objetos JSON que estamos procurando. Só que esse objeto não é um JSON.
- Mas ele tem um método para retornar o texto da resposta que tem dentro de si convertido em objeto JSON, né não?
Tem sim. Só que há um pequeno problema com isso. Esse método que transforma o texto da resposta em JSON também retorna uma promessa. Porque ele deve fazer alguma coisa que envolve espera (leitura dos dados da resposta, provavelmente).
Mas se ele retorna uma promessa, agora a gente já sabe como lidar com isso, não é mesmo?
E é mais fácil que ser campeão em cima do Flamengo em 2023. Precisamos apenas...
escrever uma função de callback que receba um parâmetro - que será o resultado da operação
chamar o método then() sobre a promessa, passando a função de callback como parâmetro
Modifique seu código. Salve, recarregue, acione o botão.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div><button id="botaoCarregar">Carregando cervejas...</button></div>
<div id="cervejasDiv"></div>
</body>
<script>
let cervejas = []
//cs é um array de cervejas
const carregarDiv = cs => {
const div = document.getElementById("cervejasDiv")
const itensHtml = cs.map( ({name,alcohol}) => `<div>${name} -- ${alcohol}</div>` )
div.innerHTML = `${itensHtml.join("\n")}`
}
async function carregarCervejas(){
try{
let res = await fetch("https://random-data-api.com/api/v2/beers?size=3")
cervejas = await res.json()
carregarDiv(cervejas)
}catch(err){
document.getElementById("cervejasDiv").innerHTML = "Fudeu..."
}
}
function chegouRespostaDoJson(json){
cervejas = json
carregarDiv(cervejas)
}
function chegouRespostaDoFetch(res){
let p = res.json()
p.then(chegouRespostaDoJson)
}
function carregarCervejas2(){
let p = fetch("https://random-data-api.com/api/v2/beers?size=3")
p.then(chegouRespostaDoFetch)
document.getElementById("cervejasDiv").innerHTML = `Fazendo requisição`
}
let botao = document.getElementById("botaoCarregar")
botao.addEventListener("click", () => carregarCervejas2() )
</script>
</html>
Ora, ora, agora sim, mudamos as coisas para que ficasse tudo como estava...
Mas podemos mudar mais ainda. Convenhamos que estava bem mais legível usar os dois await que fazer essa fuleiragem toda de função que obtém uma promessa e passa uma função de callback pra ela mas essa função de callback já recebe outra promessa e passa outra função de callback pro then dessa outra promessa. E, se essa segunda função de callback precisasse lidar com outra promessa, imagine o tamanho do moído. Tudo isso SEM fazer tratamento de erros, que são inerentes a qualquer promessa. Porque só Chicó mesmo não falha com suas promessas. Essas do JS podem falhar, sim, e nosso código tem que estar preparado pra isso.
Então vamos ajeitando mais esse código, mudando as coisas novamente para que fique tudo realmente como estava.
Sabe aquelas variáveis locais p nas expressões...
let p = fetch("https://random-data-api.com/api/v2/beers?size=3")
p.then(chegouRespostaDoFetch)
ou...
let p = res.json()
p.then(chegouRespostaDoJson)
Bem, livre-se dessas variáveis locais...
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div><button id="botaoCarregar">Carregando cervejas...</button></div>
<div id="cervejasDiv"></div>
</body>
<script>
let cervejas = []
//cs é um array de cervejas
const carregarDiv = cs => {
const div = document.getElementById("cervejasDiv")
const itensHtml = cs.map( ({name,alcohol}) => `<div>${name} -- ${alcohol}</div>` )
div.innerHTML = `${itensHtml.join("\n")}`
}
async function carregarCervejas(){
try{
let res = await fetch("https://random-data-ai.com/api/v2/beers?size=3")
cervejas = await res.json()
carregarDiv(cervejas)
}catch(err){
document.getElementById("cervejasDiv").innerHTML = "Fudeu..."
}
}
function chegouRespostaDoJson(json){
carregarDiv(json)
}
function chegouRespostaDoFetch(res){
res.json().then(chegouRespostaDoJson)
}
function carregarCervejas2(){
fetch("https://random-data-api.com/api/v2/beers?size=3").then(
chegouRespostaDoFetch
)
document.getElementById("cervejasDiv").innerHTML = `Fazendo requisição`
}
let botao = document.getElementById("botaoCarregar")
botao.addEventListener("click", () => carregarCervejas3() )
</script>
</html>
Simplificou um pouco, mas dá pra fazer mais, né?
Se ligue nessas funções chegouRespostaDoJson e chegouRespostaDoFetch.
Olhe bem para elas, olhe bem mesmo, e olhe mais.
Elas não são chamadas por você em canto nenhum, certo? São chamadas pelas promessas.
Logo, você realmente nem precisa do nome delas.
Ah, e elas recebem um só parâmetro.
E elas têm uma só linha.
Se você é um bom programador JS e olhou realmente para elas por um bom tempo, conforme eu instruí, você há de ter ouvido um gemido desesperado das funções chegouRespostaDoJson e chegouRespostaDoFetch dizendo: "nós devemos ser arrow functions anônimas, bicho de chifre!"
Transforme as funções chegouRespostaDoJson e chegouRespostaDoFetch em arrow functions anônimas, definidas no momento em que são necessárias - nas chamadas ao método then() das promessas.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div><button id="botaoCarregar">Carregando cervejas...</button></div>
<div id="cervejasDiv"></div>
</body>
<script>
let cervejas = []
//cs é um array de cervejas
const carregarDiv = cs => {
const div = document.getElementById("cervejasDiv")
const itensHtml = cs.map( ({name,alcohol}) => `<div>${name} -- ${alcohol}</div>` )
div.innerHTML = `${itensHtml.join("\n")}`
}
async function carregarCervejas(){
try{
let res = await fetch("https://random-data-ai.com/api/v2/beers?size=3")
cervejas = await res.json()
carregarDiv(cervejas)
}catch(err){
document.getElementById("cervejasDiv").innerHTML = "Fudeu..."
}
}
function carregarCervejas2(){
fetch("https://random-data-api.com/api/v2/beers?size=3").then(
res => res.json().then( json => carregarDiv(json) )
)
document.getElementById("cervejasDiv").innerHTML = `Fazendo requisição`
}
let botao = document.getElementById("botaoCarregar")
botao.addEventListener("click", () => carregarCervejas2() )
</script>
</html>
Ficou certamente mais simples. Mas há, ainda o que melhorar.
Sempre há.
A gente pode encadear aquela segunda chamada ao then. O código vai ficar mais legível e, para isso, a gente só precisa que nossa primeira callback function, a que passamos para o primeiro then retorne uma promessa. Isso é fácil, já que a chamada res.json() nos dá justamente uma promessa.
Vamos lá, é mais fácil ver no código que entender na explicação.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div><button id="botaoCarregar">Carregando cervejas...</button></div>
<div id="cervejasDiv"></div>
</body>
<script>
let cervejas = []
//cs é um array de cervejas
const carregarDiv = cs => {
const div = document.getElementById("cervejasDiv")
const itensHtml = cs.map( ({name,alcohol}) => `<div>${name} -- ${alcohol}</div>` )
div.innerHTML = `${itensHtml.join("\n")}`
}
async function carregarCervejas(){
try{
let res = await fetch("https://random-data-ai.com/api/v2/beers?size=3")
cervejas = await res.json()
carregarDiv(cervejas)
}catch(err){
document.getElementById("cervejasDiv").innerHTML = "Fudeu..."
}
}
function carregarCervejas2(){
fetch("https://random-data-api.com/api/v2/beers?size=3").then(
res => res.json()
).then(
json => carregarDiv(json)
)
document.getElementById("cervejasDiv").innerHTML = `Fazendo requisição`
}
let botao = document.getElementById("botaoCarregar")
botao.addEventListener("click", () => carregarCervejas2() )
</script>
</html>
Note que o primeiro then é chamado sobre a promessa resultante do fetch, ao passo que o segundo é chamado sobre a promessa resultante do res.json() - como no caso anterior, mas com uma sintaxe mais simples.
Fica mais fácil de ler e a gente ainda pode empurrar uma chamada para uma função de tratamento de exceção lá ao final do encadeamento.
O nome dessa função é catch e ela recebe - imaginem só! - uma callback function. Essa callback function, naturalmente, recebe o erro como parâmetro.
Modifique seu código.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div><button id="botaoCarregar">Carregando cervejas...</button></div>
<div id="cervejasDiv"></div>
</body>
<script>
let cervejas = []
//cs é um array de cervejas
const carregarDiv = cs => {
const div = document.getElementById("cervejasDiv")
const itensHtml = cs.map( ({name,alcohol}) => `<div>${name} -- ${alcohol}</div>` )
div.innerHTML = `${itensHtml.join("\n")}`
}
async function carregarCervejas(){
try{
let res = await fetch("https://random-data-api.com/api/v2/beers?size=3")
cervejas = await res.json()
carregarDiv(cervejas)
}catch(err){
document.getElementById("cervejasDiv").innerHTML = "Fudeu..."
}
}
function carregarCervejas2(){
fetch("https://random-data-api.com/api/v2/beers?size=3").then(
res => res.json()
).then(
json => carregarDiv(json)
).catch(
err => document.getElementById("cervejasDiv").innerHTML = `Fudeu... ${err}`
)
document.getElementById("cervejasDiv").innerHTML = `Fazendo requisição`
}
let botao = document.getElementById("botaoCarregar")
botao.addEventListener("click", () => carregarCervejas2() )
</script>
</html>
- Professor, e esse catch é relativo a qual dos 2 then?
É relativo aos 2.
Basicamente, dando merda em qualquer um deles, a arrow function do catch será invocada. Funciona como o bloco try/catch da carregarCervejas, na solução original...
async function carregarCervejas(){
try{
let res = await fetch("https://random-data-ai.com/api/v2/beers?size=3")
cervejas = await res.json()
carregarDiv(cervejas)
}catch(err){
document.getElementById("cervejasDiv").innerHTML = "Fudeu..."
}
}
No caso dessa função, o código do catch será executado caso haja exceção em qualquer das linhas do bloco.
- Mas se eu quiser tratar cada then com um código específico lá nas promessas?
Você terá que ser um cara chato pra querer isso, mas o then aceita um segundo parâmetro, que é uma função de callback para ser chamada caso a promessa seja rejeitada. O código a seguir trata potenciais erros em cada then separadamente... (use, se quiser)
function carregarCervejas2(){
fetch("https://random-data-api.com/api/v2/beers?size=3").then(
res => res.json(),
err => document.getElementById("cervejasDiv").innerHTML = `Fudeu no primeiro then()... ${err}`
).then(
json => carregarDiv(json),
err => document.getElementById("cervejasDiv").innerHTML = `Fudeu no segundo then()... ${err}`
)
document.getElementById("cervejasDiv").innerHTML = `Fazendo requisição`
}
E assim, voltamos, enfim, ao início, à ideia inicial de mudar as coisas para que fique tudo como está.
- Só uma coisa, professor: a abordagem com o await é mais lenta em termos de desempenho? Minha página fica travada quando chega a uma chamada com await e não fica quando chega a uma chamada com Promise/then?
Na verdade, é tudo basicamente a mesma coisa.
Até onde eu sei, e até a data em que escrevo esse texto, seu código que usa o await é que fica em espera. Mas a página rodando JS em seu navegador não espera nem por Jesus, a única maneira de travá-la (a única que eu conheço) seria executando um algoritmo que exigisse muito tempo de processamento sem fazer nenhuma operação assíncrona (como operações de entrada e saída, a exemplo do fetch). Um loop infinito ou um loop com milhões de operações em ponto flutuante (sem nenhuma operação assíncrona) numa função dessas provavelmente travariam sua página, mas são cenários bem pouco realistas na Programação Web.
Pra entender melhor isso você pode inspecionar o seu código, no navegador, e selecionar a opção Console, para executar scripts.
No Console, vamos colar esse código bem besta:
async function g(){
console.log(1)
f()
console.log(4)
}
async function f(){
console.log(2)
fetch("https://random-data-api.com/api/v2/beers?size=3").then( () => 0)
console.log(3)
}
g()
Não há nenhuma operação de espera nesse código, porque o fetch retorna uma promessa, imediatamente, como vimos, e o then também retorna imediatamente - apenas registra uma função de callback.
Chamamos a função g, que escreve 1, chama a função f, que escreve 2, chama a função fetch (depois o then), que retorna logo em seguida, sem espera. Depois a própria função f escreve 3, e retorna para a função g, que escreve 4. A saída será a exebição dos números na sequência...
Mas podemos forçar uma operação de espera lá dentro da função f, acrescentando uma palavrinha só e tirando o then.
async function g(){
console.log(1)
f()
console.log(4)
}
async function f(){
console.log(2)
await fetch("https://random-data-api.com/api/v2/beers?size=3")
console.log(3)
}
g()
A execução desse código simples demostra claramente que o uso do await não trava sua página. A função f retorna naquele momento que a gente usa o await, e o controle volta para a função g, que segue seu processamento normalmente.
Quando a chamada dependurada no await retornar um resultado, o restante da função f será executado. Assim, neste segundo cenário, o último número escrito no console será o 3...
Assim, o uso do async/await e do Promise/then é mais uma questão de estilo de programação e não de desempenho. Eu prefiro async/await. Há quem prefira Promise/then.
A gente tem que aprender as duas alternativas, principalmente você que é estudante.
E, sobretudo, a gente tem que ter ciência de que as duas abordagens envolvem sequências distintas de execução do código que escrevemos.
Procure seu novo melhor amigo, o ChatGPT, e apresente-lhe a seguinte demanda, ou algo parecido: "In the context of JavaScript, explain me the topic "Promise" as if I were a junior computer programmer with no experience in web programming".
Faça os exercícios da receita anterior, sendo que usando Promise/then/catch.