Qué son las funciones callback, cómo especificar funciones callback y funciones anónimas para resolver procesos asíncronos de Javascript.
Estamos haciendo una serie de artículos para explicar los procesos asíncronos en Javascript, una de las características más potentes que condicionan el desarrollo con este lenguaje de programación.
En este artículo vamos a ver las funciones callback, que constituyen un primer paso para trabajar y experimentar con la asincronía en el lenguaje.
Qué es un callback
Un callback es una función que se invoca cuando un proceso asíncrono ha terminado. El propio nombre puede facilitar su comprensión: una función que se llama (call) de vuelta (back), para retornar el control de flujo al código asíncrono que estamos programando.
Date cuenta que los procesos asíncronos de Javascript tienen la particularidad de ejecutarse liberando el proceso principal. Por tanto, cuando el proceso asíncrono termina queremos que el proceso principal se entere y para ello se usan las funciones callback: Para devolver el control al iniciador del proceso asíncrono, enviando generalmente el dato resultante de la ejecución de ese proceso asíncrono.
Qué es un proceso asíncrono
Por supuesto, para entender qué es un callback necesitamos saber qué es un proceso asíncrono. No es más que una operación realizada durante la programación de Javascript que tardará en ejecutarse. En realidad, todo proceso en general lleva un tiempo para su ejecución. Lo que caracteriza al proceso asíncrono es que durante este tiempo de procesamiento libera el hilo de ejecución, devolviendo inmediatamente el flujo al programa principal.
No vamos a dar más detalles en este momento porque esto ya lo hemos explicado anteriormente. Así que, para entenderlo mejor te recomendamos el artículo que explica concepto de Javascript asíncrono.
Cómo funcionan los callbacks de un proceso asíncrono
En el siguiente diagrama vamos a expresar el flujo de ejecución de un proceso asíncrono con una imagen, que esperamos sirva de guía.
Dentro del anterior diagrama tenemos etiquetados varias etapas del proceso, que describimos a continuación con más detalle.
- 1) Se está ejecutando el flujo principal hasta que llega un momento que se hace una llamada a un proceso asíncrono.
- 2a) Se hace una llamada a un proceso asíncrono
- 2b) Continúa la ejecución del proceso principal.
- 2a) y 2b) Estas acciones se ejecutan en secuencia, pero para nuestra percepción como humanos ocurren de inmediato, ya que el proceso asíncrono arranca pero toda la demora que podría producirse en él no se nota, ya que el flujo de ejecución se libera y con ello el flujo principal puede continuar su ejecución instantáneamente
- 3) El proceso asíncrono lleva su tiempo y una vez termina devuelve de nuevo el flujo de ejecución al proceso principal.
- 4) El proceso principal tiene entonces la opción de invocar la función callback, a la que generalmente se le envía el dato que el proceso asíncrono ha producido como respuesta. Por ejemplo, si fuera una llamada a la base de datos, se le entregaría el resultado de la ejecución de la consulta.
Cuándo usamos las funciones callback
Las funciones callback son una de las alternativas existentes en Javascript para lidiar con los procesos asíncronos. Otras vías de trabajo las encontramos en las Promesas.
En Javascript encontramos numerosos ejemplos de uso de funciones callback, como por ejemplo en las solicitudes Ajax. Al solicitar al servidor un recurso con Ajax el servidor puede tardar en responder. Durante ese tiempo el hilo de ejecución de Javascript puede dedicarse a otras cosas, como por ejemplo mostrar la animación del típico "loading". Una vez termina la solicitud y el navegador nos envía la respuesta, el navegador podrá usar la función callback que se haya definido, con el código que necesite ejecutarse en ese momento.
Así pues, el proceso asíncrono libera el hilo de ejecución y la función callback es llamada para recuperar ese proceso, realizando acciones una vez ya se tiene el dato que se necesitaba.
Ajax es el ejemplo más típico de asincronía en el navegador, pero en NodeJS los ejemplos son todavía más frecuentes, por ejemplo cuando se accede a las bases de datos, al sistema de archivos, etc.
Otro ejemplo muy básico de función callback lo encontramos en el uso del setTimeout()
de Javascript. Enseguida veremos ejemplos precisamente de este caso.
Cómo se definen las funciones callback
Habitualmente las funciones callback se definen mediante funciones anónimas, que son aquellas a las que simplemente nadie les ha puesto un nombre.
Para la realización de un proceso asíncrono generalmente tendremos que invocar una función que se encargará de producir ese proceso. A esa función le enviaremos como argumento la función anónima que hará de callback para retomar el control en el flujo principal de ejecución.
Qué es una función anónima
Puedes haber visto en innumerables ocasiones el uso de funciones anónimas, por ejemplo al definir manejadores de eventos.
elemento.addEventListener('click', function() {
// Esta función anónima se ejecutará cuando se hace clic sobre el elemento
});
Como has podido comprobar, esa función no tiene nombre. Se ejecutará cuando se haga clic, pero nadie la ha declarado en un espacio de nombres.
Ejemplo de código asíncrono y su función callback
El ejemplo más sencillo de código asíncrono lo tenemos con la función setTimeout()
, que se ejecuta pasados un número de milisegundos.
let x = 2;
setTimeout( function() {
console.log("x vale", x);
}, 1000);
Este código indicará en la consola "x vale 2" pasado 1 segundo (lo equivalente a 1000 milisegundos).
Explicamos la función timeout en el artículo qué es asíncrono en Javascript. Si no entiendes este código te sugiero que repases ese artículo.
Callback con una función con nombre
Aunque lo normal sea usar funciones anónimas al registrar callbacks no quiere decir que no podamos usar también una función con nombre. De hecho, aquí vemos un código diferente al anterior pero que tiene como resultado una salida idéntica.
function miCallback() {
console.log("x vale", x);
}
let x = 2;
setTimeout(miCallback, 1000);
En este punto lo que tienes que fijarte es que al invocar setTimeout()
le estamos pasando la función completa como argumento. Al enviarla como argumento no se invoca la función, ya que no hemos colocado los paréntesis después de "miCallback
".
Si le colocases los paréntesis lo que harías sería invocar la función inmediatamente, por lo que no se conseguiría ningún retardo en el mensaje.
Definir un callback con una función flecha
Podemos usar arrow functions, también llamadas funciones flecha, para especificar el callback, lo que nos permitirá tener un código bastante más compacto.
let x = 2;
setTimeout( () => console.log("x vale", x), 3000);
Además de un código más compacto, las funciones flecha tienen una diferencia fundamental en el tratamiento de la palabra "this" dentro de la función. Si no lo conoces te recomendamos leer el artículo sobre las arrow functions en Javascript.
Otros ejemplos de callbaks en Javascript para el navegador
NodeJS está repleto de funciones que usan callbacks, porque el lenguaje se usa para muchos procesos asíncronos, que no dependen directamente de Javascript sino de sistemas externos, como el sistema de archivos o sistemas gestores de bases de datos. En el navegador hay menos ejemplos de funciones que requieran callbacks, comparativamente. Algunos de ellos los vamos a ver a continuación.
Función setInterval()
La función setInterval()
es muy parecida a setTimeout()
con la diferencia de que el callback se invocará repetidas veces, cada vez que pase un intervalo de tiempo.
Por ejemplo, con el siguiente código conseguimos que haya un mensaje en la consola que nos diga el número de segundos que han pasado desde que empezó la ejecución.
let x = 0;
setInterval( function() {
x++;
console.log(`Ha pasado ${x} segundo/s`);
}, 1000);
En este ejercicio estamos usando otra cosa más o menos nueva de Javascript que son las template string. Con ellas podemos interpolar el valor de variables de una manera cómoda en las cadenas.
Objetos XMLHttpRequest para Ajax
Otro de los ejemplos típicos donde funcionamos con callbacks en Javascript es Ajax. El objeto XMLHttpRequest
es la interfaz más antigua disponible en Javascript para realizar comunicaciones asíncronas contra servicios web, o sea: Ajax.
Con Ajax encontramos un "ejemplo de libro" en lo que respecta a la asincronía en Javascript, ya que las comunicaciones con otros servidores son el ejemplo más típico de procesos que van a tardar un rato. Durante ese tiempo de espera antes de recibir la respuesta del servidor, algo no depende del propio lenguaje Javascript, el hilo de ejecución debe liberarse para que el navegador pueda atender otras cuestiones.
El caso es que el objeto XMLHttpRequest
tiene una manera diferente de definir las funciones callback, básicamente porque lo podemos configurar con múltiples funciones, para cada uno de los distintos casos posibles de respuesta. Por ejemplo, cuando una llamada al servidor recibió una respuesta correcta o cuando hubo un error.
Básicamente lo que se hace con XMLHttpRequest
es definir una serie de eventos que nos permiten ejecutar código cuando pasan cosas en el objeto XMLHttpRequest
. Cada uno de esos eventos los puedes asociar con manejadores de eventos, que se ejecutarán cuando éstos ocurran.
Podríamos decir que los manejadores de eventos son una especie de callbacks. Aunque sería justo aclarar que son cosas distintas. Sin embargo, en el fondo son funciones que se registran para ejecutarse cuando ocurren cosas, por lo que las vamos a ver en este artículo dedicado a callbacks. Es verdad que técnicamente XMLHttpRequest
funciona con eventos, pero en el fondo los podemos entender como callbacks. Solo habría una diferencia entre eventos y callbacks. Mientras que los callbacks a menudo reciben de vuelta el dato que se ha conseguido por medio del proceso asíncrono, los manejadores de eventos reciben por parámetro el objeto evento.
En el siguiente código encuentras un ejemplo de trabajo con XMLHttpRequest
y la definición de una función anónima como manejador de evento onload
. Como decía, no es exactamente un callback pero funciona de manera muy similar.
// creación del objeto XMLHttpRequest
let xhr = new XMLHttpRequest();
// definición de la consulta Ajax, asíncrona, que vamos a realizar
xhr.open("GET", 'https://jsonplaceholder.typicode.com/todos/1');
// definición de un manejador de eventos al recibir el resultado de la llamada ajax
xhr.onload = function(e) {
// Miro si la llamada está finalizada y el estado es 404
if(xhr.readyState == 4 && xhr.status == 404) {
console.log('Error de recurso no encontrado');
}
// muestro el cuerpo de la respuesta
console.log(xhr.responseText);
}
// ejecuto la llamada
// nota que por el hecho de hacer la llamada a open() solo se configura la solicitud
// para que realmente se realice hay que llamar a send()
xhr.send();
Otros mecanismos de trabajo con los procesos asíncronos
Durante mucho tiempo los procesos asíncronos se gestionaron únicamente mediante callbacks. Actualmente se siguen usando mucho, pero existen otras maneras de gestionarlos en Javascript, un poco más avanzadas y útiles, ya que nos permiten una mejor organización del código.
El siguiente punto que te recomiendo estudiar son las promesas de Javascript, pero también existe el async await para gestionar los procesos asíncronos.
Miguel Angel Alvarez
Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...