JavaScript Assíncrono: O Guia Completo - Parte 4 - Usando Promises

1 de agosto de 2022
Ronaldo B.

Neste artigo iremos falar sobre promises. Se desejar, você pode conferir as outras postagens dessa série nestes links:

  1. JavaScript Assíncrono: O Guia Completo - Parte 1 - O que é um código assíncrono?
  2. JavaScript Assíncrono: O Guia Completo - Parte 2 - Callbacks
  3. JavaScript Assíncrono: O Guia Completo - Parte 3 - Treinando Callbacks

Nos últimos artigos aprendemos sobre callbacks, e vimos como essas funções nos ajudam em requisições assíncronas. Contudo, em junho de 2015, chegou para nós a nova versão do EcmaScript, a versão ES2015, ou ES6. Uma das novidades que essa versão trouxe foi o conceito de promises. Elas vieram para facilitar a passagem de funções de callback e também nos ajudar a trabalhar com códigos assíncronos

O que são promises?

“Promise” significa promessa. Então, as promises representam uma promessa: elas tentarão executar alguma função para nós e nos deixarão informados se conseguiu ou não cumprir sua missão. Em outras palavras, uma promise é um objeto que representa a falha ou sucesso de um código.

Para entendermos melhor, pense nessa ilustração: quando vamos a uma entrevista de emprego, temos o objetivo óbvio de conseguir tal emprego. Após a entrevista, geralmente o representante da empresa onde fomos nos diz a seguinte frase: “Se seu perfil nos agradar, entraremos em contato com você.” Isso é uma promessa, e a gente torce para que ela se torne realidade. Contudo, a ligação dessa empresa confirmando a nossa contratação é algo que está além do nosso controle. Pode ser que dê certo, mas pode ser que dê errado, nós não podemos controlar isso. O que podemos fazer é nos preparar para a resposta, seja ela qual for. Esse é o conceito de uma promise. Nós informamos uma função para ela. Pode ser que dê certo ou não, a gente não sabe o que irá acontecer. O que podemos fazer é nos preparar para um sucesso ou para uma falha na execução da função. Isso é algo sensacional, pois nos dá um maior controle sobre nosso código, inclusive na manipulação de erros, algo que as funções de callback não nos oferecem por padrão.

Sintaxe básica de uma promise

Para usar uma promise, é necessário criar uma nova instância do objeto Promise. Esse objeto irá esperar uma função como argumento, que por sua vez irá esperar dois parâmetros: 1) resolve, 2) reject. Ambos são funções:

1) resolve(). É a  função que confirmará o sucesso da operação se tudo der certo na promessa.

2) reject(). É a função que confirmará a “falha” da operação se algo der errado na promessa. Isso pode significar um erro ou simplesmente uma exceção, algo que fuja do que esperamos para a nossa função.

Apenas um detalhe: esses nomes nos ajudam a entender o objetivo de cada função. Contudo, como elas são representadas por variáveis, podemos dar o nome que desejarmos para elas.

Veja abaixo a criação de uma promise:

function addNumbers(numberA, numberB) {
	return new Promise((resolve, reject) => {
		let result = numberA + numberB;
		if (result > 0) {
			resolve(result);
		} else {
			reject('O resultado é inválido');
		}
	});
}
addNumbers(2, 5);

Explicando: nós criamos uma função que irá somar dois números. Essa função retorna uma promessa, com os parâmetros que falamos acima: resolve e reject. Perceba que usamos arrow functions nesse exemplo. Dentro da promessa nós fazemos uma verificação: se o resultado da conta for maior que zero, ou seja, se houver um resultado, iremos executar o resolve(), informando que tudo ocorreu bem. Perceba que iremos inclusive já informar qual foi o resultado de fato. Contudo, se não houver resultado, iremos rejeitar a promessa com o método reject(), deixando claro que ocorreu algum erro ou exceção, por meio de uma string. Isso nos ensina outra coisa: podemos informar como parâmetro para essas funções qualquer tipo de dado: uma string, uma variável, um número ou até mesmo uma outra promessa.

Além dos métodos resolve() e reject(), as promessas também possuem dois métodos bem interessantes: 1) Promise.all() e 2) Promise.race()

1) Promise.all(). Espera como parâmetro uma série de promessas e irá retornar um sucesso (resolve) quando todas elas executarem corretamente, ou retornará uma falha (reject) se alguma promessa não for executada completamente. É um método usado quando precisamos executar várias promessas ao mesmo tempo.

2) Promise.race(). Espera como parâmetro uma série de promessas e irá retornar um sucesso (resolve) assim que a primeira promessa executar corretamente, ou retornará uma falha (reject) quando uma promessa não for completamente executada.

Percebemos que é bem simples criar uma promessa. Mas, como iremos manipular aquilo que ela nos retornar?

“Callback” nas Promises

As promises nos oferecem dois métodos que podemos usar para manipular os possíveis resultados: 1) then e 2) catch

1) then(). Esse método recebe o que o nosso resolve() informar. É usado para manipular a resposta de sucesso da promessa.

2) catch(). Esse método recebe o que o nosso reject() informar. É usado para manipular a resposta de erro ou exceção da promessa.

Esses métodos serão definidos no local onde a promessa foi chamada. Veja um exemplo abaixo:

addNumbers(2, 5)
	.then(result => {
		console.log(`O resultado é ${result}`);
	})
	.catch(error => {
		console.error(`Ocorreu um erro: ${error}`);
	});

Explicando: Os métodos foram executados abaixo da chamada de addNumbers(). Algo muito legal que podemos perceber é que é possível quebrar linhas ao chamar esses métodos, o que deixa o código mais fácil de ler. Além disso, note que as informações que nós retornamos na criação da promessa foram identificadas no código acima como duas variáveis: result e error. Novamente, como elas são variáveis, podemos ficar à vontade para escolher seus nomes.

E agora podemos entender um detalhe muito importante: como esses dois métodos são executados após a execução de uma função principal, que é a função contida em nossa promise, podemos dizer que o then() e o catch() se enquadram no conceito de funções de callback. Sensacional, não concorda? Podemos ver então que as promises são uma maneira alternativa de criar funções de callback. É possível até afirmar que é melhor usar as promises. Por que dizemos isso?

Vantagens das Promises

Uma das principais vantagens de usar promises ao invés de callbacks é o que chamamos de encadeamento. Isso representa chamar várias funções em sequência, uma após a outra. Quando fazemos isso com callbacks, nosso código pode ficar um tanto “bagunçado”, difícil de entender e de dar manutenção. Veja este exemplo: vamos criar três funções que irão exibir uma informação no console, usando a sintaxe “clássica” de funções de callback. As funções são as seguintes:

function writeHello(callback) {
	console.log('Hello');
	callback();
}

function writeGoodBye(callback2) {
	console.log('GoodBye');
	callback2();
}

function writeSeeYouLater(callback3) {
	console.log('See you later');
	callback3();
}

Cada uma delas vai chamar seu respectivo callback. Vamos realizar a chamada delas:

writeHello(function(){
	writeGoodBye(function() {
		writeSeeYouLater(function() {
			console.log('Acabou o código')
		})
	})
})

Esse código representa um encadeamento. Nós chamamos uma função após a outra. Mas tem uma questão: o código não ficou um tanto estranho? Está parecendo uma pirâmide de códigos, não é mesmo? Se essa estrutura crescer um pouco mais, já vai ficar difícil dar a manutenção necessária para essas funções. Veja agora como ficaria a criação dessas mesmas funções com promises:

function writeHello() {
	return new Promise((s, f) => {
		console.log('Hello');
		s();
	});
}

function writeGoodBye() {
	return new Promise((s2, f) => {
		console.log('GoodBye');
		s2();
	});
}

function writeSeeYouLater() {
	return new Promise((s3, f) => {
		console.log('See you later');
		s3();
	});
}

Perceba que agora chamamos o resolve com a letra “s”, representando o sucesso da execução, mas poderia ser qualquer outro identificador.

Veja abaixo como ficaria agora a chamada dessas funções:

writeHello()
	.then(writeGoodBye())
	.then(writeSeeYouLater())
	.then(() => {
		console.log('Acabou o código');
	})

Acabamos de converter nosso código para um encadeamento de promessas, que vai continuar funcionando tranquilamente. Contudo, nosso código agora está bem mais intuitivo, mais simples, mais fácil de entender. Pode ser que a gente até precise escrever um pouco mais, mas o código está bem mais organizado.

Concluímos assim que as promises são uma evolução dos callbacks e elas facilitam bastante a chamada de códigos assíncronos. O que acha de colocar o conceito de promises em prática no projeto da Hcode Games, que estamos desenvolvendo juntos? Vamos lá então!

Precisamos lembrar onde estávamos usando callbacks no código: era no arquivo requests.js. Ele será alterado primeiro. As funções getGameByName() e getRelatedGamesByName() esperavam uma variável chamada callback; isso não será mais necessário

Agora as duas funções irão retornar promessas, com as variáveis resolve e reject como parâmetro. Além disso, podemos adaptar a maneira de realizar a requisição. Nós estávamos usando o objeto XMLHttpRequest. Contudo, podemos usar uma nova ferramenta que o JavaScript nos dá: a Fetch API. Ela é bem mais simples de usar para realizar requisições para um servidor, e está sendo chamada de “o novo AJAX”

Para usar esse recurso basta chamar a função fetch(). Ela espera como parâmetro a URL de requisição; e o melhor: consegue imaginar o que ela nos retorna? Uma promessa :). Veja abaixo como ficará a nossa função getGameByName():

function getGameByName(name) {
	return new Promise((resolve, reject) => {
		fetch(`https://api.rawg.io/api/games?search=${name}`)
			.then(res => res.json())
			.then(data => {
				resolve(data);
			})
			.catch(err => {
				reject(err);
			})
		});
}

Explicando: dentro da promessa que criamos nós chamamos a função fetch(). Como essa função nos retorna uma promessa, utilizamos os método .then() e .catch().

Lembrando que nós falamos sobre Promises e Fetch API também em nosso Curso Completo de JavaScript.

Voltando para o código. Perceba que criamos dois métodos .then(). Por quê? O primeiro irá converter a resposta do servidor para JSON; esse código é correspondente a executarmos a função JSON.parse(), como estava no código anterior, mas agora está bem mais simples. Note assim que podemos chamar mais de um método .then() na mesma promessa, se desejarmos.

Outro detalhe importante é que agora a mensagem de erro ou os resultados em JSON serão retornados por meio do reject() ou resolve(). Vamos seguir o mesmo padrão para a função getRelatedGamesByName():

function getRelatedGamesByName(name) {
	return new Promise((resolve, reject) => {
		fetch(`https://api.rawg.io/api/games/${name}/suggested`)
			.then(res => res.json())
			.then(data => {
				resolve(data);
			})
			.catch(err => {
				reject(err);
			})
		});
}

Perfeito! Agora podemos ir para nosso arquivo HTML.

Nós chamamos essas funções apenas na função initGames() e será ela quem iremos alterar. Agora iremos informar apenas o nome do jogo para as funções e ao invés de adicionar o código de callback em uma função anônima, iremos usar o método .then(). O código final ficará assim:

function initGames(gamename) {

   setGameLoad(gamesListPrincipal);

   gamesListRelationed.innerHTML = '';

   getGameByName(gamename).then(games => {

       gamesListPrincipal.innerHTML = '';

       games.results.forEach(game => {

           let divGame = setGameHTML(game);

           divGame.addEventListener('click', e => {

               setGameLoad(gamesListRelationed);

               let gameTag = e.currentTarget;

               let gamename = gameTag.dataset.gamename;

               getRelatedGamesByName(gamename).then(gamesRelationed => {

                   gamesListRelationed.innerHTML = '';

                   gamesRelationed.results.forEach(game => {

                       let divGameRelationed = setGameHTML(game);

                       gamesListRelationed.append(divGameRelationed);

                   });

               });

           });

           gamesListPrincipal.append(divGame);

       });

   });

}

Alterações finalizadas! Agora, como dizia o título do primeiro Karate Kid: “Agora é a hora da verdade”. Nosso projeto precisa continuar funcionando. Vamos testá-lo:

Lista de jogos da Hcode Games
Lista de jogos da Hcode Games

Ótimo, continua funcionando igual antes, mas agora nosso projeto está modelado no conceito das Promises.

Nesse artigo aprendemos o que são as Promises no JavaScript, e eu prometo que elas irão te ajudar bastante no desenvolvimento (perdão pelo trocadilho, não consegui aguentar). Você pode encontrar todos os códigos desenvolvidos nesse artigo neste link: https://github.com/hcodebr/javascript-assincrono-blog

Se você gostou do conteúdo desse artigo, pedimos que o compartilhe, para que outras pessoas também possam ter acesso a essa matéria, muito obrigado!

As promessas são realmente incríveis, muito fáceis de implementar. Mas, o JavaScript possui ainda um outro recurso, introduzido em sua última versão, que também ajuda bastante em códigos assíncronos, facilitando ainda mais o uso das promessas. Estamos falando das funções assíncronas, ou async/await. Nós iremos falar sobre elas com mais detalhes no próximo artigo: JavaScript Assíncrono: O Guia Completo - Parte 5 - Entendendo Async/Await, até lá :)

Hcode: Utilizamos cookies para a personalização de anúncios e experiências de navegação dentro de nosso site. Ao continuar navegando, você concorda com as nossas Política de Privacidade.