En esta página

Async/Await

12 min lectura TextoCap. 4 — Asincronía

Qué es async/await?

async/await es azucar sintáctico sobre las promesas que hace que el código asincrono se lea como si fuera sincrono. Introducido en ES2017, es la forma preferida de trabajar con operaciones asincronas en JavaScript moderno.

La palabra clave async

Marcar una función como async tiene dos efectos:

  1. La función siempre retorna una promesa
  2. Permite usar await dentro de ella
async function ejemplo() {
  return 42; // equivale a return Promise.resolve(42)
}

La palabra clave await

await pausa la ejecución de la función async hasta que la promesa se resuelva, y retorna su valor:

async function obtener() {
  const resultado = await algunaPromesa(); // pausa aquí
  console.log(resultado); // se ejecuta cuando la promesa se resuelve
}

Manejo de errores

En lugar de .catch(), usamos bloques try/catch que son familiares de la programación sincrona:

async function cargar() {
  try {
    const datos = await operacionRiesgosa();
    return datos;
  } catch (error) {
    console.error('Fallo:', error.message);
    return valorPorDefecto;
  } finally {
    limpiarRecursos();
  }
}

El bloque catch captura tanto errores de la promesa rechazada como excepciones lanzadas con throw.

Secuencial vs paralelo

Este es uno de los errores más comunes con async/await:

Secuencial (lento)

const a = await peticion1(); // espera 1s
const b = await peticion2(); // espera 1s más
// Total: 2 segundos

Paralelo (rápido)

const [a, b] = await Promise.all([peticion1(), peticion2()]);
// Total: 1 segundo (se ejecutan al mismo tiempo)

Usa la versión secuencial solo cuando una petición depende del resultado de la anterior.

Patrones útiles

Procesar una lista de items

Dependiendo de si los items son independientes o no:

  • Secuencial — Usa for...of con await dentro del bucle
  • Paralelo — Usa .map() con funciones async y Promise.all

Retry con backoff exponencial

Un patrón robusto para reintentar operaciones que pueden fallar temporalmente (ej. peticiones de red). Cada reintento espera exponencialmente más tiempo (1s, 2s, 4s...).

Top-level await

En módulos ES, puedes usar await directamente en el scope del módulo:

// config.js (módulo ES)
const response = await fetch('/api/config');
export const config = await response.json();

async/await vs .then()

Aspecto async/await .then()
Legibilidad Más legible, flujo lineal Puede anidarse
Errores try/catch familiar .catch() en cadena
Debugging Stack traces claros Stack traces fragmentados
Paralelismo Necesita Promise.all Natural con multiples .then

En la práctica, async/await se prefiere para la mayoria de los casos. Usa .then() cuando necesites composicion de promesas rápida o en callbacks cortos.


Práctica

  1. Convierte .then() a async/await: Toma una cadena de promesas existente con .then().catch() y reescribela usando async/await con try/catch/finally. Verifica que el comportamiento sea identico.
  2. Ejecuta tareas en paralelo: Escribe una funcion async que use Promise.all para ejecutar 3 llamadas simuladas (usando setTimeout envuelto en promesas) en paralelo, y mide el tiempo total con performance.now().
  3. Implementa un retry basico: Crea una funcion conReintentos(fn, intentos) que reintente ejecutar fn hasta intentos veces si falla. Probala con una funcion que falle aleatoriamente.

En la siguiente leccion aprenderemos a usar la Fetch API para hacer peticiones HTTP.

await secuencial vs paralelo
Multiples awaits seguidos se ejecutan secuencialmente. Si las operaciones son independientes, usa Promise.all para ejecutarlas en paralelo y reducir el tiempo total.
Top-level await
En módulos ES (archivos con import/export), puedes usar await fuera de funciones async. Esto es útil para inicializacion de módulos y scripts.
javascript
// Función async - siempre retorna una promesa
async function obtenerUsuario(id) {
  // await pausa la ejecución hasta que la promesa se resuelva
  const respuesta = await fetch(`/api/users/${id}`);
  const usuario = await respuesta.json();
  return usuario;
}

// Manejo de errores con try/catch
async function cargarDatos() {
  try {
    const usuario = await obtenerUsuario(1);
    const posts = await fetch(`/api/users/${usuario.id}/posts`);
    const datos = await posts.json();
    console.log('Posts:', datos);
  } catch (error) {
    console.error('Error al cargar:', error.message);
  } finally {
    console.log('Carga finalizada');
  }
}

// Peticiones en paralelo con await
async function cargarDashboard() {
  // MAL: secuencial (lento)
  // const users = await fetch('/api/users').then(r => r.json());
  // const posts = await fetch('/api/posts').then(r => r.json());

  // BIEN: paralelo (rápido)
  const [users, posts] = await Promise.all([
    fetch('/api/users').then(r => r.json()),
    fetch('/api/posts').then(r => r.json()),
  ]);

  return { users, posts };
}

// Arrow function async
const obtenerPerfil = async (id) => {
  const res = await fetch(`/api/profiles/${id}`);
  if (!res.ok) {
    throw new Error(`HTTP ${res.status}`);
  }
  return res.json();
};
javascript
// Patron: procesar items secuencialmente
async function procesarSecuencial(urls) {
  const resultados = [];
  for (const url of urls) {
    const res = await fetch(url);
    const data = await res.json();
    resultados.push(data);
  }
  return resultados;
}

// Patron: procesar items en paralelo
async function procesarParalelo(urls) {
  const promesas = urls.map(async (url) => {
    const res = await fetch(url);
    return res.json();
  });
  return Promise.all(promesas);
}

// Patron: retry con backoff exponencial
async function conReintentos(fn, intentos = 3) {
  for (let i = 0; i < intentos; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === intentos - 1) throw error;
      const espera = Math.pow(2, i) * 1000;
      console.log(`Reintentando en ${espera}ms...`);
      await new Promise(r => setTimeout(r, espera));
    }
  }
}

// Uso del retry
const datos = await conReintentos(
  () => fetch('/api/datos').then(r => r.json()),
  3
);