Saltar al contenido principal
09Programación Asíncrona

async/await

Clase 5 de 58 min de lectura

¿Y si pudieras escribir código asíncrono que se leyera igual que el síncrono, línea a línea y sin cadenas de .then()? Eso es async/await: la forma más moderna y cómoda de trabajar con promesas.

No es un mecanismo nuevo, es azúcar sintáctico sobre las Promises. Por debajo sigue habiendo promesas, solo cambia cómo las escribes.

¿Qué son async/await?#

Son dos palabras clave que trabajan en equipo:

  • async declara que una función es asíncrona.
  • await pausa la ejecución de esa función hasta que una Promise se resuelva.

Lo de «pausa la ejecución» es una simplificación útil, pero conviene matizarla. await no bloquea todo el programa. Lo que le dice al motor de JavaScript es: «esto va a tardar; mientras espero, sal de esta función y sigue ejecutando otro código». Cuando la promesa se resuelve, el motor vuelve justo a ese punto y continúa con el resto de la función.

Piensa en un chef. Cuando mete un pastel en el horno no se queda mirando la puerta: pone un temporizador y se pone con otros platos. Cuando suena, vuelve al horno y saca el pastel. async/await hace que tu código se comporte como ese chef.

Infografía
await pausa la función, no el programa
5.La función termina e imprime el último mensaje. El orden final es 1, 2, 3: await pausa la función, no el programa.
Paso 5 de 5

Comparación visual

Vamos a escribir la misma función de las dos maneras. Primero con promesas y .then(), que funciona, pero obliga a leer saltando de callback en callback:

// Con Promises (funcional pero verboso)function obtenerDatos() {  return fetch('/api/datos')    .then(response => response.json())    .then(datos => {      console.log(datos)      return datos    })    .catch(error => {      console.log('Error:', error)    })}

Y ahora con async/await. Fíjate en dónde aparece cada palabra clave:

// Con async/await (mucho más limpio)async function obtenerDatos() {  try {    const response = await fetch('/api/datos')    const datos = await response.json()    console.log(datos)    return datos  } catch (error) {    console.log('Error:', error)  }}

La receta para convertir una en otra es siempre la misma:

  1. Añade async delante de la función.
  2. Cambia cada .then() por un await y guarda el resultado en una variable.
  3. Cambia el .catch() por un try/catch, el mismo que usas con código síncrono.

Características importantes

Hay tres reglas del juego que debes tener claras.

1. Una función async siempre devuelve una Promise. Aunque devuelvas un valor normal, JavaScript lo envuelve en una promesa ya resuelta:

async function miFuncion() {  return 42;}console.log(miFuncion()) // -> Promise { 42 }miFuncion().then(resultado => {  console.log(resultado) // -> 42})// o con awaitconst resultado = await miFuncion()console.log(resultado) // -> 42

Por eso, para obtener el 42, necesitas un .then() o un await. Si usas la llamada tal cual, lo que tienes es la promesa.

2. Solo puedes usar await dentro de funciones async. Si lo usas en una función normal, el código ni siquiera llega a ejecutarse:

function miFuncion() {  await new Promise(resolve => setTimeout(resolve, 1000)) // ❌ falta async  return 42}// SyntaxError: await is only valid in async functions// and the top level bodies of modules

3. await pausa la función hasta que la Promise se resuelva. Aquí lo medimos con console.time:

async function miFuncion() {  await new Promise(resolve => setTimeout(resolve, 1000))  // hasta que no pasa un segundo,  // no se ejecuta la siguiente línea  return 42}async function ejecutar() {  console.time('miFuncion')  await miFuncion() // 42  console.timeEnd('miFuncion')  // miFuncion: 1000.004ms (1 segundo)}ejecutar()

El return 42 no se ejecuta hasta que pasa el segundo, y ejecutar tampoco avanza hasta que miFuncion termina.

Cuidado con las operaciones en paralelo#

Poner un await detrás de otro es tan cómodo que es fácil caer en un error muy común: hacer secuencial algo que podría ir en paralelo. Es como hacer una sola cola en el supermercado cuando hay tres cajas libres.

Imagina que cargas la página de perfil de un usuario. Necesitas tres cosas: su información básica, su lista de amigos y sus últimas publicaciones. Para ello tienes tres funciones: obtenerInfoBasica(), obtenerAmigos() y obtenerPublicaciones().

❌ Secuencial (más lento)

La primera idea es pedirlas una detrás de otra. Mira cuánto tarda cada una:

async function cargarPerfilSecuencial(userId) {  console.time('Perfil lento');  // 1. Pido los datos del usuario y espero...  const usuario = await obtenerInfoBasica(userId); // tarda 1 segundo  // 2. Cuando terminan, pido sus amigos y espero...  const amigos = await obtenerAmigos(userId); // tarda 1 segundo  // 3. Cuando terminan, pido sus publicaciones y espero...  const publicaciones = await obtenerPublicaciones(userId); // tarda 2 segundos  console.timeEnd('Perfil lento'); // Suma el tiempo de las 3 llamadas  // Tiempo total: 4 segundos  console.log('Datos cargados secuencialmente:', { usuario, amigos, publicaciones });}

Cada llamada espera a que acabe la anterior, así que el tiempo total es la suma de todas: 4 segundos.

Pero vuelve a mirar el código: ninguna llamada necesita el resultado de otra. Las tres solo usan userId. No hay ningún motivo para que esperen su turno.

✅ Paralelo (más rápido)

Aquí entra Promise.all, que ya conoces. Lanzamos las tres promesas a la vez y hacemos un único await sobre el conjunto:

async function cargarPerfilParalelo(userId) {  console.time('Perfil más rápido')  // Iniciamos todas las operaciones al mismo tiempo  // y esperamos a que todas terminen  const [usuario, amigos, publicaciones] = await Promise.all([    obtenerInfoBasica(userId), // tarda 1 segundo    obtenerAmigos(userId),  // tarda 1 segundo    obtenerPublicaciones(userId) // tarda 2 segundos  ])  console.timeEnd('Perfil más rápido')  // Tiempo total: 2 segundos  return {    usuario,    amigos,    publicaciones  }}cargarPerfilParalelo(123).then(perfil => {  console.log('✅ Perfil cargado:', perfil.usuario.nombre)  console.log('👥 Amigos:', perfil.amigos.length)  console.log('📝 Publicaciones:', perfil.publicaciones.length)})

Ahora el tiempo total es el de la llamada más lenta: 2 segundos, la mitad que antes. Y sin cambiar ni una sola de las tres funciones.

Infografía
await en secuencia frente a Promise.all
5.…y solo esperamos a la más lenta: 2 segundos. La mitad de tiempo con los mismos datos.
Paso 5 de 5

Un último repaso rápido a async/await#

Con esta clase cierras el recorrido por la asincronía en JavaScript, y cada paso ha ido arreglando lo que fallaba en el anterior:

EtapaQué aportaSu punto débil
CallbacksLa base: «cuando acabes, me avisas»El Callback Hell
PromisesCadenas lineales y errores en un solo .catch()Muchos .then() encadenados
async/awaitCódigo asíncrono que se lee como síncronoEs fácil olvidarse del paralelismo

Las tres siguen conviviendo: async/await funciona sobre promesas, y muchas APIs siguen pidiendo callbacks. Ahora tienes todas las herramientas para manejar cualquier operación asíncrona.

En resumen

  • async/await es azúcar sintáctico sobre las Promises: el código asíncrono se lee como si fuera síncrono.
  • Una función async siempre devuelve una Promise.
  • await pausa la función, no el programa, y solo se puede usar dentro de funciones async.
  • Los errores se manejan con try/catch.
  • Si las operaciones son independientes, lánzalas a la vez con Promise.all.