JavaScript Assíncrono: O Guia Completo - Parte 2 - Callbacks
Neste artigo iremos falar sobre as funções de callback e como elas ajudaram no desenvolvimento do JavaScript Assíncrono. Você pode conferir o conteúdo da parte 1 desta série neste link:
JavaScript Assíncrono: O Guia Completo - Parte 1 - O que é um código assíncrono?
O que são callbacks
Uma função de callback é basicamente uma função executada após outra. Para que essa função seja executada, geralmente a informamos como parâmetro de uma função inicial. Para entendermos melhor esse conceito, pense no seguinte exemplo: quando você era criança, já recebeu alguma tarefa da sua mãe? Talvez você se lembre de ter recebido uma lista de tarefas. Exemplo: “lave a louça, varra a sala e organize seu quarto.” Você fez tudo isso. Mas, no final do dia, ela te diz: “Esqueci de mais uma coisa: leve o lixo pra fora.” Percebeu? Você fez várias tarefas, mas por fim ela te passou mais uma para fazer. Uma função de callback funciona da mesma maneira. Nós executamos uma função, que irá realizar várias ações. Mas informamos também uma função no parâmetro, como que dizendo: “depois de executar tudo que eu já defini, realize mais essa tarefa que está nos parâmetros.”
Mas, por que deveríamos usar uma função de callback? Em alguns casos, nós só queremos executar uma ação depois que algo já ocorreu na página. Exemplo: imagine que desejamos renderizar uma lista de cidades em uma tabela. Contudo, as informações das cidades estão vindo de uma API. Então, quando iremos realizar a renderização? Apenas quando tivermos os dados de fato, não é mesmo? Nesse caso, poderíamos criar uma função de renderização que seria o callback da função principal, que seria a responsável por trazer os dados.
Dois exemplos clássicos de funções de callback são as funções setTimeout() e setInterval(), que são nativas do JavaScript. Ambas esperam uma função de retorno. Mas, o que acha de criarmos juntos uma função de callback? Veja o código abaixo:
function writeHello(callback) {
console.log('Hello World');
if (typeof callback == 'function') callback();
}
Nós criamos uma função chamada writeHello() que irá exibir “Hello World” no console. Mas, além disso, esperamos como parâmetro uma variável chamada callback. E perceba que na última linha da função nós verificamos qual é o tipo da variável, para garantir que ela é uma função e depois chamamos essa variável com parênteses no fim de seu nome, deixando claro que ela é uma função de callback
Veja agora como poderíamos executar a função writeHello():
writeHello(function() {
console.log('Callback Function');
});
Bem interessante, não acha? Nós chamamos a função writeHello() e já informamos uma função anônima, uma função de callback, que também exibirá uma mensagem no console.
Se acessarmos o console ao executar este código, veremos o seguinte resultado:
As duas mensagens foram exibidas, pois as duas funções foram executadas.
Esse é o conceito das funções de callback. Mas, conforme falamos no artigo anterior, iremos criar um site que irá consumir uma API e exibir as informações na tela. O que acha de começarmos a desenvolver esse projeto, já colocando em prática o que aprendemos sobre callbacks? Preparados? Então, vamos lá!
Todos os códigos que desenvolvermos estarão disponíveis em nosso GitHub
Primeiro, vamos explicar a estrutura do projeto:
Possuímos uma pasta para arquivos de estilo, uma pasta para os arquivo JS, uma pasta para os arquivos de imagem e por fim o nosso arquivo HTML, arquivo de entrada para o projeto.
Na questão de ferramentas externas, iremos usar apenas o Materialize.css para deixar nossa página um pouco mais agradável e simples de estilizar.
De início, nosso projeto está assim:
Agora sim, vamos para a lógica do projeto. Nosso objetivo é digitar o nome de um jogo na caixa de busca e ter os resultados renderizados na área de jogos principais. Em primeiro lugar, vamos precisar de uma função para consumir a API de jogos. Vamos criar essas funções dentro de um arquivo chamado requests.js, que ficará dentro da pasta js.
A primeira função se chamará getGameByName() e esperará dois parâmetros: o nome do jogo que estamos procurando e uma função de callback, que é o tema desse artigo. Seu código será o seguinte:
function getGameByName(name, callback) {
let request = new XMLHttpRequest();
request.open('GET', `https://api.rawg.io/api/games?search=${name}`, true);
request.onload = () => {
let response = JSON.parse(request.response);
if (typeof callback == 'function') callback(response);
};
request.send();
}
Explicando a função: nós iremos criar uma requisição XML por meio da classe XMLHttpRequest(). Lembrando que nós ensinamos os diversos recursos dessa classe e como manipulá-la em nosso Curso Completo de JavaScript.
Continuando a explicação. Após criar essa requisição XML, nós iremos acessar a URL da API, informando o nome que esperamos lá nos argumentos da função. Quando conseguirmos obter os dados nós iremos convertê-los para JSON e, note agora, executar a função de callback, informando os dados dos jogos para essa função. Assim, poderemos fazer o que desejarmos com essas informações, ou seja, poderemos renderizá-las na tela. Precisamos agora chamar essa função que acabamos de criar. O nome dessa função será initGames().
Antes disso, voltando no arquivo HTML, vamos abrir uma tag <script> no fim dele e criar duas funções e duas variáveis que serão muito úteis.
As variáveis que iremos criar serão a nossa referência para os elementos HTML que irão conter os jogos principais e os jogos relacionados. Segue o código da criação delas:
const gamesListPrincipal = document.querySelector('#games-list-principal');
const gamesListRelationed = document.querySelector('#games-relationed');
As duas funções úteis que iremos criar são as seguintes: 1) setGameLoad(). Ela será a responsável por adicionar um elemento de “loading” (carregamento) em nossa página quando estivermos esperando pelo retorno dos dados da API. Seu código é este:
function setGameLoad(gameEl) {
gameEl.innerHTML = `
<div class="preloader-wrapper active" style="position: absolute; left: 50%; margin-top: 30px">
<div class="spinner-layer spinner-blue-only">
<div class="circle-clipper left">
<div class="circle"></div>
</div>
<div class="gap-patch">
<div class="circle"></div>
</div>
<div class="circle-clipper right">
<div class="circle"></div>
</div>
</div>
</div>
</div>
`;
}
Explicando seu conteúdo: nós iremos esperar uma variável chamada gameEl como parâmetro. Essa variável representa o elemento onde será aplicado o “loading”. Iremos selecionar esse elemento e mudar o seu conteúdo HTML por meio da propriedade innerHTML, adicionando então o HTML padrão do Materialize.css, ou seja, o “loading” de fato.
Segunda função: 2) setGameHTML(). Essa função irá criar o template de cada jogo que virá da API. Por meio desta função iremos definir o HTML de cada item e como suas informações serão exibidas para nosso usuário. Seu código é este:
function setGameHTML(game) {
let div = document.createElement('div');
div.dataset.gamename = game.slug;
div.className = 'item';
div.innerHTML = `
<div class="card">
<div class="card-image">
<img src="${game.background_image}" />
</div>
<div class="card-content">
<p>
<h5>${game.name}</h5> (${game.released})
</p>
</div>
</div>
`;
return div;
}
Explicando seu conteúdo: iremos criar de maneira virtual uma tag <div>, através do método createElement(). Adicionaremos o nome do jogo no dataset desta tag e definiremos seu HTML, baseando-nos no componente “Card” do Materialize.css. O template mostrará as seguintes informações: o nome do jogo, a data de seu lançamento e a sua foto.
Depois de criar essas funções e variáveis, vamos agora de fato para a função initGames(). Ela irá esperar um parâmetro chamado gamename, representando o nome do jogo que estamos querendo. Assim que chamarmos essa função, queremos que o elemento de “loading” já apareça, dando para o usuário um feedback visual, para que ele não fique impaciente, sem saber o que está ocorrendo. Para isso, já iremos chamar a função setGameLoad(), informando a variável gamesListPrincipal, que criamos acima.
Chegamos agora em um momento muito importante. Iremos chamar a função getGameByName(), passando como parâmetro o nome do jogo, que já temos armazenado na variável gamename, e também uma função de callback. O código ficará assim:
function initGames(gamename) {
setGameLoad(gamesListPrincipal);
getGameByName(gamename, function(games) {
gamesListPrincipal.innerHTML = '';
games.results.forEach(game => {
let divGame = setGameHTML(game);
gamesListPrincipal.append(divGame);
});
});
}
Explicando: nós chamamos a função getGameByName() e informamos no segundo parâmetro uma função anônima que, como já definimos lá no arquivo requests.js, será executada apenas quando já tivermos os dados em nossas mãos. Essa função anônima primeiramente retirou o conteúdo da área de nosso site que conterá os jogos, garantindo que nenhuma informação anterior irá sobrescrever a nova. Depois, realizamos um laço de repetição nos resultados da requisição, usando o método forEach(). Dentro dessa função nós criamos o HTML de cada item usando a função setGameHTML(), que já foi desenvolvida e por fim adicionamos esse novo elemento à div que lista os jogos.
É basicamente dessa maneira que nosso projeto irá funcionar. O que precisamos definir agora é o seguinte: quando a função initGames() será chamada. O que acha de chamarmos essa função após o usuário escrever o nome do jogo que está procurando e tirar o foco do campo de busca? Existe um evento para isso: blur. Veja como defini-lo no código abaixo:
document.querySelector('[type=text]').addEventListener('blur', e => {
initGames(e.target.value);
});
Explicando: nós selecionamos o campo <input> do tipo texto que foi criado em nossa página e adicionamos um ouvinte para o evento “blur”. Ao ocorrer esse evento, iremos chamar a função initGames(), informando como parâmetro o valor desse mesmo campo <input>. Conseguimos essa informação através do objeto de evento.
Estamos prontos para testar nosso projeto. Vamos acessar o arquivo HTML no navegador, digitar o nome de algum jogo no campo de busca e clicar fora desse campo, o que irá disparar o evento. O resultado é o seguinte:
Está funcionando perfeitamente! Lembrando que é possível arrastar os itens com o mouse, o que deixa essa página bem moderna. Sensacional, não é mesmo? Fizemos isso usando o conceito de funções de callback, esperamos que tenham gostado.
Você pode encontrar os códigos desenvolvidos nesse artigo neste link: https://github.com/hcodebr/javascript-assincrono-blog
Mas ainda está faltando uma coisa: imagine que possamos clicar em algum jogo e ver embaixo os jogos relacionados a ele. Seria basicamente mais uma requisição para a API. Que tal fazermos isso com callbacks também? Vamos desenvolver esse código no próximo artigo: JavaScript Assíncrono: O Guia Completo - Parte 3 - Treinando Callbacks, que já sai amanhã. Então, fica de olho nas nossas novidades e até o próximo artigo :)