Tabla de contenido

Si llevas un tiempo trabajando con JavaScript, seguramente te has topado con código que parece difícil de leer por la cantidad de funciones anidadas. Esto tiene nombre: callback hell, y async/await es la solución moderna para ese problema.

¿Qué es la programación asíncrona?

Antes de entrar en materia, necesitamos entender qué significa que algo sea asíncrono. Imagina que le pides una pizza. Tú no te quedas parado en la puerta esperando a que llegue, sino que sigues haciendo otras cosas y cuando llega, te avisan. Eso es la programación asíncrona: el programa no se detiene esperando una respuesta, sigue ejecutando otras instrucciones.

En JavaScript esto es especialmente importante porque es un lenguaje single-threaded, es decir, solo puede hacer una cosa a la vez. Para no bloquear la ejecución mientras espera respuestas de una API o archivos, JavaScript utiliza el modelo asíncrono.

El problema: Callback Hell

La forma más antigua de manejar asincronía en JavaScript fue mediante callbacks, que básicamente son funciones que se ejecutan cuando algo termina. El problema surge cuando tienes muchas operaciones encadenadas:

obtenerUsuario(id, function(usuario) {
  obtenerPedidos(usuario.id, function(pedidos) {
    obtenerDetalle(pedidos[0].id, function(detalle) {
      console.log(detalle)
    })
  })
})

JavaScript

Este código funciona, pero es difícil de leer, difícil de depurar y difícil de mantener. A esto se le llama callback hell o también “la pirámide de la perdición”.

Promesas en JavaScript

Las Promesas (Promises) llegaron para solucionar el callback hell. Una promesa representa un valor que estará disponible en el futuro, ya sea de forma exitosa o con un error.

obtenerUsuario(id)
  .then(usuario => obtenerPedidos(usuario.id))
  .then(pedidos => obtenerDetalle(pedidos[0].id))
  .then(detalle => console.log(detalle))
  .catch(error => console.error(error))

JavaScript

Mucho mejor, ¿no? Pero aún puede mejorarse más con async/await.

async/await al rescate

async/await es azúcar sintáctica sobre las promesas. No reemplaza a las promesas, simplemente hace que el código asíncrono se lea como si fuera síncrono.

Para usarlo necesitas dos palabras reservadas:

  • async: Se coloca antes de la declaración de una función para indicar que es asíncrona
  • await: Se usa dentro de una función async para esperar a que una promesa se resuelva

Mira cómo queda el ejemplo anterior con async/await:

async function obtenerInfoUsuario(id) {
  const usuario = await obtenerUsuario(id)
  const pedidos = await obtenerPedidos(usuario.id)
  const detalle = await obtenerDetalle(pedidos[0].id)
  console.log(detalle)
}

JavaScript

¡Mucho más limpio y legible! Parece código síncrono, pero por debajo sigue siendo asíncrono.

Manejo de errores con try/catch

Una de las ventajas de async/await es que puedes manejar errores con la estructura try/catch que ya conoces de la programación tradicional:

async function obtenerInfoUsuario(id) {
  try {
    const usuario = await obtenerUsuario(id)
    const pedidos = await obtenerPedidos(usuario.id)
    console.log(pedidos)
  } catch (error) {
    console.error("Ocurrió un error:", error)
  }
}

JavaScript

Si cualquiera de las operaciones dentro del try falla, el control pasa directamente al bloque catch, donde puedes manejar el error de forma centralizada.

Ejemplo práctico con fetch

El uso más común de async/await en el día a día es al consumir APIs con fetch. Aquí un ejemplo real:

async function obtenerPersonajes() {
  try {
    const respuesta = await fetch("https://rickandmortyapi.com/api/character")
    const datos = await respuesta.json()
    console.log(datos.results)
  } catch (error) {
    console.error("Error al obtener personajes:", error)
  }
}

obtenerPersonajes()

JavaScript

Observa que usamos await dos veces: una para esperar la respuesta HTTP y otra para convertir esa respuesta a JSON, ya que .json() también devuelve una promesa.

Algunos puntos importantes que debes recordar:

  1. Solo puedes usar await dentro de funciones marcadas como async
  2. async/await no elimina las promesas, las hace más legibles
  3. Siempre acompaña tu código con try/catch para manejar errores
  4. Puedes ejecutar múltiples promesas en paralelo con Promise.all si no dependen una de la otra

Con esto ya tienes las bases para trabajar con async/await en tus proyectos. Es una de las características más utilizadas en JavaScript moderno y dominarlo te abrirá muchas puertas.