En este artículo hablaremos sobre las funciones de callback y cómo han ayudado en el desarrollo de JavaScript Asíncrono. Puedes consultar el contenido de la parte 1 de esta serie en este enlace:
JavaScript Asíncrono: La Guía Completa - Parte 1 - ¿Qué es un código asíncrono?
¿Qué son los callbacks?
Una función de callback es básicamente una función que se ejecuta después de otra. Para que esta función se ejecute, generalmente la informamos como parámetro de una función inicial. Para entender mejor este concepto, piensa en el siguiente ejemplo: cuando eras niño, ¿alguna vez recibiste una tarea de tu madre? Tal vez recuerdes haber recibido una lista de tareas. Ejemplo: “lava los platos, barre la sala y organiza tu cuarto.” Hiciste todo eso. Pero, al final del día, ella te dice: “Olvidé otra cosa: saca la basura.” ¿Lo notaste? Hiciste varias tareas, pero al final ella te pasó una más para hacer. Una función de callback funciona de la misma manera. Ejecutamos una función, que realizará varias acciones. Pero también informamos una función en el parámetro, como diciendo: “después de ejecutar todo lo que ya definí, realiza esta otra tarea que está en los parámetros.”
Pero, ¿por qué deberíamos usar una función de callback? En algunos casos, solo queremos ejecutar una acción después de que algo ya ha ocurrido en la página. Ejemplo: imagina que deseamos renderizar una lista de ciudades en una tabla. Sin embargo, la información de las ciudades está viniendo de una API. Entonces, ¿cuándo realizaremos la renderización? Solo cuando tengamos los datos de hecho, ¿no es así? En este caso, podríamos crear una función de renderización que sería el callback de la función principal, que sería la responsable de traer los datos.
Dos ejemplos clásicos de funciones de callback son las funciones setTimeout() y setInterval(), que son nativas de JavaScript. Ambas esperan una función de retorno. Pero, ¿qué te parece si creamos juntos una función de callback? Mira el código a continuación:
function writeHello(callback) {
console.log('Hello World');
if (typeof callback == 'function') callback();
}
Hemos creado una función llamada writeHello() que mostrará “Hello World” en la consola. Pero, además de eso, esperamos como parámetro una variable llamada callback. Y observa que en la última línea de la función verificamos cuál es el tipo de la variable, para garantizar que es una función y luego llamamos a esa variable con paréntesis al final de su nombre, dejando claro que es una función de callback
Ahora veamos cómo podríamos ejecutar la función writeHello():
writeHello(function() {
console.log('Callback Function');
});
Bien interesante, ¿no crees? Llamamos a la función writeHello() y ya informamos una función anónima, una función de callback, que también mostrará un mensaje en la consola.
Si accedemos a la consola al ejecutar este código, veremos el siguiente resultado:
Los dos mensajes se mostraron, ya que ambas funciones se ejecutaron.
Este es el concepto de las funciones de callback. Pero, como mencionamos en el artículo anterior, vamos a crear un sitio que consumirá una API y mostrará la información en la pantalla. ¿Qué te parece si comenzamos a desarrollar este proyecto, poniendo en práctica lo que aprendimos sobre callbacks? ¿Listos? ¡Entonces, vamos!
Todo el código que desarrollemos estará disponible en nuestro GitHub
Primero, expliquemos la estructura del proyecto:
Tenemos una carpeta para archivos de estilo, una carpeta para los archivos JS, una carpeta para los archivos de imagen y por último nuestro archivo HTML, archivo de entrada para el proyecto.
En cuanto a herramientas externas, solo utilizaremos Materialize.css para hacer que nuestra página sea un poco más agradable y fácil de estilizar.
Al principio, nuestro proyecto es así:
Ahora sí, vamos a la lógica del proyecto. Nuestro objetivo es escribir el nombre de un juego en la caja de búsqueda y hacer que los resultados se rendericen en el área de juegos principales. En primer lugar, necesitaremos una función para consumir la API de juegos. Vamos a crear estas funciones dentro de un archivo llamado requests.js, que estará dentro de la carpeta js.
La primera función se llamará getGameByName() y esperará dos parámetros: el nombre del juego que estamos buscando y una función de callback, que es el tema de este artículo. Su código será el siguiente:
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 la función: vamos a crear una solicitud XML a través de la clase XMLHttpRequest(). Recuerda que enseñamos los diversos recursos de esta clase y cómo manipularla en nuestro Curso Completo de JavaScript.
Continuando con la explicación. Después de crear esta solicitud XML, vamos a acceder a la URL de la API, informando el nombre que esperamos allí en los argumentos de la función. Cuando obtengamos los datos vamos a convertirlos a JSON y, nota ahora, ejecutar la función de callback, informando los datos de los juegos a esta función. Así, podremos hacer lo que deseemos con esa información, es decir, podremos renderizarlas en la pantalla. Necesitamos ahora llamar a esta función que acabamos de crear. El nombre de esta función será initGames().
Antes de eso, volviendo al archivo HTML, abramos una etiqueta al final de él y crearemos dos funciones y dos variables que serán muy útiles.
Las variables que vamos a crear serán nuestras referencias para los elementos HTML que contendrán los juegos principales y los juegos relacionados. Aquí está el código para su creación:
const gamesListPrincipal = document.querySelector('#games-list-principal');
const gamesListRelationed = document.querySelector('#games-relationed');
Las dos funciones útiles que vamos a crear son las siguientes: 1) setGameLoad(). Será la responsable de añadir un elemento de “loading” (cargando) en nuestra página cuando estemos esperando el retorno de los datos de la API. Su código es 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 su contenido: vamos a esperar una variable llamada gameEl como parámetro. Esta variable representa el elemento donde se aplicará el “loading”. Vamos a seleccionar ese elemento y cambiar su contenido HTML a través de la propiedad innerHTML, añadiendo así el HTML estándar de Materialize.css, o sea, el “loading” de hecho.
Segunda función: 2) setGameHTML(). Esta función creará la plantilla de cada juego que vendrá de la API. A través de esta función definiremos el HTML de cada ítem y cómo se mostrarán sus informaciones a nuestro usuario. Su código es 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 su contenido: vamos a crear de manera virtual una etiqueta Después de crear estas funciones y variables, vamos ahora de hecho a la función initGames(). Esta esperará un parámetro llamado gamename, representando el nombre del juego que queremos. Así que, tan pronto como llamemos a esta función, queremos que el elemento de “loading” ya aparezca, dando al usuario un feedback visual, para que no se impacienten, sin saber qué está ocurriendo. Para eso, ya llamaremos a la función setGameLoad(), informando la variable gamesListPrincipal, que creamos arriba. Ahora llegamos a un momento muy importante. Vamos a llamar a la función getGameByName(), pasando como parámetro el nombre del juego, que ya tenemos almacenado en la variable gamename, y también una función de callback. El código quedará así: Explicando: llamamos a la función getGameByName() e informamos en el segundo parámetro una función anónima que, como ya definimos en el archivo requests.js, se ejecutará solo cuando ya tengamos los datos en nuestras manos. Esta función anónima primero eliminó el contenido del área de nuestro sitio que contendrá los juegos, asegurando que ninguna información anterior sobrescriba la nueva. Luego, realizamos un bucle de repetición en los resultados de la solicitud, usando el método forEach(). Dentro de esta función creamos el HTML de cada ítem usando la función setGameHTML(), que ya fue desarrollada y finalmente añadimos este nuevo elemento a la div que lista los juegos. Es básicamente de esta manera que nuestro proyecto funcionará. Lo que necesitamos definir ahora es lo siguiente: ¿cuándo se llamará la función initGames()? ¿Qué te parece si llamamos a esta función después de que el usuario escriba el nombre del juego que está buscando y quite el foco del campo de búsqueda? Hay un evento para esto: blur. Mira cómo definirlo en el código a continuación: Explicando: seleccionamos el campo de tipo texto que fue creado en nuestra página y añadimos un oyente para el evento “blur”. Cuando ocurra este evento, vamos a llamar a la función initGames(), informando como parámetro el valor de este mismo campo . Conseguimos esta información a través del objeto del evento. Estamos listos para probar nuestro proyecto. Vamos a acceder al archivo HTML en el navegador, escribir el nombre de algún juego en el campo de búsqueda y hacer clic fuera de este campo, lo que desencadenará el evento. El resultado es el siguiente: ¡Está funcionando perfectamente! Recordando que es posible arrastrar los elementos con el mouse, lo que hace que esta página sea muy moderna. Sensacional, ¿no? Hicimos esto utilizando el concepto de funciones de callback, esperamos que te haya gustado. Puedes encontrar los códigos desarrollados en este artículo en este enlace: https://github.com/hcodebr/javascript-assincrono-blog Pero todavía falta algo: imagina que podamos hacer clic en algún juego y ver abajo los juegos relacionados con él. Sería básicamente otra solicitud a la API. ¿Qué tal si hacemos eso con callbacks también? Vamos a desarrollar este código en el próximo artículo: JavaScript Asíncrono: La Guía Completa - Parte 3 - Entrenando Callbacks, que saldrá mañana. ¡Entonces, mantente atento a nuestras novedades y hasta el próximo artículo :)function initGames(gamename) {
setGameLoad(gamesListPrincipal);
getGameByName(gamename, function(games) {
gamesListPrincipal.innerHTML = '';
games.results.forEach(game => {
let divGame = setGameHTML(game);
gamesListPrincipal.append(divGame);
});
});
}
document.querySelector('[type=text]').addEventListener('blur', e => {
initGames(e.target.value);
});