Callbacks en JavaScript

Javascript

En este tutorial vamos a ver qué son las funciones callback de JavaScript y para qué se utilizan. También veremos cómo puedes crearlas y cuáles son sus problemas asociados.

Qué es un callback

Las funciones callback son las mismas funciones de siempre de JavaScript. No disponen de una sintaxis especial, ya que sencillamente son funciones que son pasadas como argumento a otra función.

La función que recibe el callback como argumento recibe el nombre de función de mayor orden. Cualquier función puede ser usada como callback, ya que basta con pasársela a otra función como parámetro.

Las funciones callback no son asíncronas por naturaleza, pero suelen ser usadas con dicho propósito, cuando por ejemplo son pasadas como argumento a los diferentes eventos que aceptan las APIs de un navegador, permitiendo que JavaScript pueda interactuar con el DOM de una página o con el sistema.

Cómo crear un callback

Para crear un callback basta con que declares una función, se la pases a otra función como parámetro y la ejecutes en el interior de dicha función. A continuación puedes ver un ejemplo de un callback.

// Una función
function saludo() {
  console.log('Hola!');
}

// Función que acepta otra función como parámetro
function hablar(callback) {
  callback(); // A esta llamada se le denomina callback
}

// Pasamos una función a otra
hablar(saludo);

Lo que hemos hecho en el ejemplo ha sido definir la función saludo() y la función hablar(), que acepta otra función como argumento. Luego ejecutamos la función hablar() a la que pasamos la función saludo() como parámetro.

Al ejecutar el código anterior obtendremos esta salida:

Hola!

Asincronismo con callbacks

JavaScript no es un lenguaje asíncrono por naturaleza. Sin embargo, los callbacks de JavaScript suelen usarse para crear código asíncrono cuando los utilizamos con las APIs del entorno de ejecución de JavaScript, ya sea un navegador o el entorno de Node.js. Por ejemplo, puedes pasar una función como callback a los eventos del navegador, como por ejemplo los eventos onClick, onMouseOver u onChange.

No sabrás cuando un usuario hará clic en un botón, pero puedes crear un handler que gestione el evento cuando suceda. El handler acepta una función como callback, que será ejecutada cuando se inicie el evento:

document.getElementById('#boton').addEventListener('click', () => {
  console.log('Se ha hecho clic en el botón');
});

También es muy habitual agregar código al evento load del objeto window del navegador, que ejecutará la función callback que definamos cuando la página se haya cargado y el DOM esté listo:

window.addEventListener('load', () => {
  console.log('Página cargada');
});

No solamente usamos callbacks para gestionar eventos del DOM del navegador, ya que otro uso muy habitual de las funciones callback se da en los eventos setTimeout, que nos permiten ejecutar la función que le pasemos como parámetro cuando transcurra el tiempo que definamos en milisegundos:

setTimeout(() => {
  console.log('Ha pasado un segundo');
}, 1000);

Con el tiempo, las simples páginas HTML de los años 90 se han convertido en aplicaciones dinámicas que se ejecutan en tu navegador. Las aplicaciones suelen realizar peticiones a APIs localizadas en diferentes servidores. Lo más habitual a día de hoy es que estas peticiones se ejecuten de forma asíncrona, de forma transparente al usuario, como es el caso de las peticiones XHR, que aceptan una función callback como parámetro.

En el siguiente ejemplo asignamos una función a la propiedad onreadystatechange de un objeto de tipo XMLHttpRequest. La función que asignemos será ejecutada como callback cuando se reciba una respuesta a la petición:

const xhr = new XMLHttpRequest();

xhr.onreadystatechange = () => {
  if (xhr.readyState === 4) {
   
    // Comprobamos si la petición se ha completado con éxito
    if (xhr.status === 200) {
      console.log(xhr.responseText);
    } else {
      // Ha ocurrido un error
      console.error('error');
    }
  }
}

// Iniciamos la petición
xhr.open('GET', 'https://api.tld/endpoint');
xhr.send();

De no usar una función asíncrona, el navegador tendría que estar comprobando todo el rato si se ha recibido una respuesta, bloqueando la ejecución del código JavaScript en el navegador, ya que JavaScript es un lenguaje síncrono y monohilo por naturaleza.

También usamos callbacks cuando usamos la API fetch de JavaScript, que es uno de los mejores modos que existen a la hora de realizar peticiones asíncrinas.

Gestión de errores en callbacks

Existen diversas estrategias que permiten gestionar errores en callbacks cuando estos se ejecutan de foma asíncrona. Por ejemplo, lo más habitual en el entorno Node.js y en la gran mayoría de las APIs del navegador consiste en que el primer parámetro de una función callback sea un objeto que contenga un posible error. A esta filosofía se le suele llamar error-first callbacks.

A continuación puedes ver un ejemplo en el que leemos un archivo del sistema:

fs.readFile('/file.json', (error, data) => {
  if (error !== null) {
    // Gestión de errores
    console.log(error);
    return;
  }

  // No ha ocurrido ningún error
  console.log(data);
})

El objeto que contiene el posible error tendrá el valor null si no ha ocurrido ningún error. De haber algún error, el objeto contendrá la descripción del error, además de otra información.

Problema común de los callbacks

Las funciones callback son ideales para casos simples, pero no están excentas de problemas cuando el código se complica.

Cuando anidamos varios callbacks, cada uno de ellos agrega un nivel de profundidad a la cola de mensajes. Si bien JavaScript puede gestionar estas situaciones sin problema, el código puede volverse difícil de leer. Además, también será más difícil saber en dónde ha ocurrido un error.

A continuación anidamos cuatro callbacks, lo cual no es extraño, pero seguramente te encuentres con casos mucho más extremos:

window.addEventListener('load', () => {
  document.getElementById('button').addEventListener('click', () => {
    setTimeout(() => {
      items.forEach(item => {
        // Código
      })
    }, 2000)
  });
});

A esta situación se le conoce como callback hell. Sin embargo, podemos evitarlas usando las alternativas de las que dispone JavaScript.

Alternativas a las funciones callback

Desde la versión ES2015 de JavaScript se han introducido varias funcionalidades que te permiten lidiar con el código JavaScript asíncrono, evitando el callback hell. Se trata de las promesas y del uso de Asinc/Await:

Y esto ha sido todo.


Avatar de Edu Lazaro

Edu Lázaro: Ingeniero técnico en informática, actualmente trabajo como desarrollador web y programador de videojuegos.

👋 Hola! Soy Edu, me encanta crear cosas y he redactado esta guía. Si te ha resultado útil, el mayor favor que me podrías hacer es el de compatirla en Twitter 😊

Si quieres conocer mis proyectos, sígueme en Twitter.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

“- Hey, Doc. No tenemos suficiente carretera para ir a 140/h km. - ¿Carretera? A donde vamos, no necesitaremos carreteras.”