Entendendo JavaScript: Promises
Júlio Guedes
Posted on September 17, 2019
No início da programação em JavaScript as coisas geralmente vão bem: é fácil entender a sintaxe da linguagem e assimilar a outras linguagens popularmente conhecidas, como Python e Java. Ao entrar mais a fundo nas possibilidades de implementação com JavaScript, surge o "bicho papão" para todo iniciante: as Promises.
Entretanto, embora pareçam assustadoras, as Promises são essenciais enquanto programando JavaScript e, com um pouco de prática, passam a ser rotina. Neste post, discutiremos o que são Promises, como utilizá-las, além de um plus pro async/await.
Antes de tudo, assíncrono
Diferente de algumas outras linguagens, como Python e Java, JavaScript permite a programação de forma assíncrona, isto é, as linhas de código de uma função podem não ser executadas uma após a outra: as linhas 1, 2 e 3 serem executadas uma após a outra não é garantia de que a linha 4 será a próxima a ser executada.
Fetch
Durante o restante desse tutorial utilizaremos o fetch
como base para os exemplos. Agora nativo no JavaScript, a API do fetch nos permite fazer requisições HTTP sem utilizar libs externas. Sumarizando bastante, as requisições são pedidos de informação de fontes externas à nossa aplicação, seja de um servidor, uma API, ou outras fontes, e o fetch
será a função que nos permite solicitar essa informação no nosso código.
Além do fetch
, existem libs que permitem fazer requisições HTTP: um exemplo forte na comunidade é a axios. Alguns frameworks, como o Angular.js, possuem funções internas ($http, no caso do Angular.js) que permitem fazer essas requisições.
O que são Promises?
Agora sim, Promises. Como explicado na sessão anterior, as requisições buscam informação em fontes externas. Entretanto, o nome fetch não foi escolhido ao acaso: pedir informação de uma fonte externa é como jogar uma bola para o cachorro ir buscar — em inglês, a expressão go fetch é usada no lugar do pega, que é geralmente usado aqui no Brasil — mas, assim como o cachorro demora para trazer a bola de volta, a informação também demora a chegar do servidor.
Mas, JavaScript é assíncrono: se a linha 3 demorar demais, como impedir que a linha 4 execute? As Promises surgem então nesse contexto: no nosso código, uma Promise (promessa, em português) serve para impedir que a linha 4 execute antes da linha 3, pois era necessária a informação adquirida na linha 3 antes de executar a linha 4. A semântica básica das promises é do this ... then do that
— faça isso ... depois faça aquilo
.
Infelizmente, assim como na vida real, nem toda promessa é cumprida, e precisamos nos precaver para esse tipo de situação. A semântica, nesse caso, lembra um pouco o try catch
de Java: se não é possível recuperar a informação ou houve erro no código do then
, caímos num catch
.
Mesmo que o código dentro do nosso then
não cause erros, erros lançados pelo servidor e erros na comunicação precisam ser tratados, por exemplo: quando o serviço externo no qual se está buscando a informação não está online, nossa requisição gera um erro de timeout; se não estamos conectados à internet no momento em que a requisição é feita, nossa requisição gera um erro de network. Detalhes como esse precisam ser tratados, e muitas vezes é isso que transforma as promises em algo delicado.
Exemplos de Promise
Para fins de exemplo, nosso serviço externo será o Laguinho da OpenDevUFCG.
Quando fizermos uma requisição GET para o Laguinho, ele deve nos retornar {"status":"running"}
. Assim, utilizando o fetch, vejamos o código necessário para fazer essa requisição:
const url = 'https://laguinho.opendevufcg.org/';
const resposta = fetch(url);
console.log('Resposta:', resposta);
Quando executamos o código acima, o console.log
nos retorna um objeto do tipo Promise
, e não o objeto com o status, que deveria ser o retorno. Vamos aplicar a semântica de promises ao código:
const url = 'https://laguinho.opendevufcg.org/';
fetch(url).then((resposta) => {
console.log('Resposta:', resposta);
});
Temos então um objeto do tipo Response
, e a partir dele é possível obter os dados da resposta dada pelo serviço externo. Já que o Laguinho nos retorna um objeto em JSON, aplicaremos a função json
na resposta para obter os dados. Note que a função json
também é uma promise.
const url = 'https://laguinho.opendevufcg.org/';
fetch(url).then((resposta) => {
console.log('Resposta:', resposta);
resposta.json().then((respostaDoServidor) => {
console.log('Resposta do Servidor:', respostaDoServidor);
});
});
Até agora, todos os nossos casos funcionam. Mas, e se houvéssemos escrito a url incorreta? Como provavelmente não haveria um servidor que se comunica utilizando JSON na url que (por exemplo) trocamos uma letra, a requisição iria falhar. Nesse caso, precisamos de um catch, para impedir que seja lançada uma exceção, e podermos tratar de acordo. Vejamos:
const url = 'https://laguinoh.opendevufcg.org/';
fetch(url).then((resposta) => {
console.log('Resposta:', resposta);
resposta.json().then((respostaDoServidor) => {
console.log('Resposta do Servidor:', respostaDoServidor);
});
}).catch((erro) => {
console.log('Aconteceu um erro durante a requisição:', erro);
});
Agora você já deve ter entendido o que são promises, como elas aparecem no seu código, e como lidar com elas. Mas, em termos de legibilidade de código, ainda há um problema: se existem diversas promises que estão uma dentro da outra, torna-se muito difícil entender o fluxo de execução. Vejamos então uma forma diferente de escrever Promises.
Plus: Async/Await
Agora, os mimos. Async/await é uma forma sensacional e bastante legível de escrever Promises, pois além de não ser verborrágica, não torna o código ilegível, seja pelo fluxo ou pelas indentações, mas é necessário encapsular o código em uma função. Vejamos os códigos anteriores quando escrito usando async/await:
const url = 'https://laguinho.opendevufcg.org/';
const fluxo = async () => {
try {
const resposta = await fetch(url);
const respostaServidor = await resposta.json();
console.log('resposta do servidor', respostaServidor);
} catch(erro) {
console.log('Aconteceu um erro durante a requisição:', erro);
}
};
Note que, não importa em que linha de código o erro aconteceu, ele sempre cairá no bloco do catch
. Se você não gostou da ideia de ter um bloco try/catch
no seu código, saiba que você não é o único: eu costumo escrever as promises utilizando uma mistura dos dois estilos, por exemplo:
const url = 'https://laguinho.opendevufcg.org/';
const fluxo = async () => {
const resposta = await fetch(url).catch((erro) => {
console.log('Erro no fetch', erro);
});
const respostaServidor = await resposta.json().catch((erro) => {
console.log('Erro no .json', erro);
});
console.log('resposta do servidor', respostaServidor);
};
Async/Await está se tornando cada vez mais popular, mas o estilo não agrada a todos: fica a seu critério decidir entre usar ou não :)
Chegamos ao fim desse post... :(
Agradeço bastante pela leitura e sinta-se à vontade para tirar quaisquer dúvidas que ainda existam. Se esse tutorial foi útil, dá uma olhada no meu outro post, introduzindo conceitos de teste em JavaScript.
Caso queira entrar em contato, só mandar um Tweet! Te convido a continuar lendo textos meus, confere meu Medium.
Se estiver buscando posts sobre programação em Português, te recomendo dar uma olhada no conteúdo do OpenDevUFCG. Se curtir desenvolvimento OpenSource, aproveita e dá uma olhada no GitHub também!
Posted on September 17, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.