Volver al curso

JavaScript Desde Cero: Tu Primer Lenguaje de Programación

leccion
17 / 22
beginner
8 horas
JavaScript Asíncrono

Async/Await: JavaScript Asíncrono Moderno

Lectura
40 min~7 min lectura

Async/Await: JavaScript Asíncrono Moderno

Objetivos de aprendizaje

Al finalizar esta lección serás capaz de:

  • Usar async/await para escribir código asíncrono que parece síncrono
  • Manejar errores con try/catch en funciones async
  • Combinar async/await con Promise.all para ejecución en paralelo
  • Aplicar patrones profesionales como retry, timeout y sequential processing
  • Migrar código de callbacks y .then() a async/await

1. De .then() a async/await

async/await es azúcar sintáctica sobre Promises. No reemplaza las Promises — las hace más fáciles de leer y escribir. El código se lee de arriba a abajo, como si fuera síncrono.

Sintaxis básica

// Función async: SIEMPRE retorna una Promise
async function obtenerDatos() {
  return "Hola"; // Equivale a: return Promise.resolve("Hola")
}

obtenerDatos().then(dato => console.log(dato)); // "Hola"

// await: esperar a que una Promise se resuelva
async function cargarUsuario() {
  console.log("Cargando...");
  
  const usuario = await obtenerUsuario(1); // Espera 1 segundo
  console.log("Usuario:", usuario.nombre);
  
  const pedidos = await obtenerPedidos(usuario.id); // Espera 1 segundo más
  console.log(`Pedidos: ${pedidos.length}`);
  
  return { usuario, pedidos };
}

cargarUsuario();

Comparación: .then() vs async/await

// ❌ Con .then() (funcional pero verboso)
function cargarDatosConThen() {
  return obtenerUsuario(1)
    .then(usuario => {
      console.log("Usuario:", usuario.nombre);
      return obtenerPedidos(usuario.id);
    })
    .then(pedidos => {
      console.log(`Pedidos: ${pedidos.length}`);
      return calcularTotal(pedidos);
    })
    .then(total => {
      console.log(`Total: $${total}`);
    })
    .catch(error => {
      console.error("Error:", error.message);
    });
}

// ✅ Con async/await (más legible)
async function cargarDatosConAwait() {
  try {
    const usuario = await obtenerUsuario(1);
    console.log("Usuario:", usuario.nombre);
    
    const pedidos = await obtenerPedidos(usuario.id);
    console.log(`Pedidos: ${pedidos.length}`);
    
    const total = await calcularTotal(pedidos);
    console.log(`Total: $${total}`);
  } catch (error) {
    console.error("Error:", error.message);
  }
}

Arrow functions async

// Arrow function async
const obtenerNombre = async (id) => {
  const usuario = await obtenerUsuario(id);
  return usuario.nombre;
};

// En event listeners
document.querySelector("#btn").addEventListener("click", async () => {
  const datos = await cargarDatos();
  renderizar(datos);
});

// En métodos de objetos
const api = {
  async getUsuarios() {
    const respuesta = await fetch("/api/usuarios");
    return respuesta.json();
  },
  
  async crearUsuario(datos) {
    const respuesta = await fetch("/api/usuarios", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(datos)
    });
    return respuesta.json();
  }
};

2. Manejo de errores con try/catch
async function operacionRiesgosa() {
  try {
    const usuario = await obtenerUsuario(1);
    const pedidos = await obtenerPedidos(usuario.id);
    const pago = await procesarPago(pedidos[0]);
    
    return { exito: true, pago };
  } catch (error) {
    console.error("Error capturado:", error.message);
    return { exito: false, error: error.message };
  } finally {
    console.log("Operación finalizada");
    // Limpiar loading spinner, cerrar conexiones, etc.
  }
}

// Manejar diferentes tipos de error
async function cargarRecurso(url) {
  try {
    const respuesta = await fetch(url);
    
    if (!respuesta.ok) {
      throw new Error(`HTTP ${respuesta.status}: ${respuesta.statusText}`);
    }
    
    const datos = await respuesta.json();
    return datos;
  } catch (error) {
    if (error instanceof TypeError) {
      // Error de red (sin conexión, CORS, etc.)
      console.error("Error de red:", error.message);
    } else if (error.message.startsWith("HTTP")) {
      // Error del servidor
      console.error("Error del servidor:", error.message);
    } else {
      // Error inesperado
      console.error("Error desconocido:", error);
    }
    return null;
  }
}

Errores en funciones async sin try/catch

// Si no atrapás el error dentro de la función async,
// se propaga como un reject de la Promise retornada
async function puedefFallar() {
  const dato = await operacionInestable(); // Puede lanzar error
  return dato;
}

// Opción 1: catch en la llamada
puedeFallar().catch(err => console.error("Atrapado afuera:", err));

// Opción 2: try/catch donde se llama
async function principal() {
  try {
    await puedeFallar();
  } catch (err) {
    console.error("Atrapado:", err);
  }
}

3. Patrones profesionales con async/await

Ejecución en paralelo con await + Promise.all

async function cargarDashboard() {
  // ❌ SECUENCIAL — tarda la suma de todas (lento)
  const stats = await fetchStats();          // 1s
  const notificaciones = await fetchNotifs(); // 1s
  const tareas = await fetchTareas();        // 1s
  // Total: ~3 segundos
  
  // ✅ PARALELO — tarda lo que la más lenta (rápido)
  const [stats, notificaciones, tareas] = await Promise.all([
    fetchStats(),       // 1s ┐
    fetchNotifs(),      // 1s ├─ en paralelo
    fetchTareas()       // 1s ┘
  ]);
  // Total: ~1 segundo
  
  return { stats, notificaciones, tareas };
}

Procesar array secuencialmente

// Secuencial (uno por uno)
async function procesarPedidosSecuencial(pedidos) {
  const resultados = [];
  for (const pedido of pedidos) {
    const resultado = await procesarPedido(pedido);
    resultados.push(resultado);
    console.log(`Pedido ${pedido.id} procesado`);
  }
  return resultados;
}

// Paralelo (todos a la vez)
async function procesarPedidosParalelo(pedidos) {
  return Promise.all(
    pedidos.map(pedido => procesarPedido(pedido))
  );
}

// Paralelo con límite de concurrencia
async function procesarConLimite(items, fn, limite = 3) {
  const resultados = [];
  for (let i = 0; i < items.length; i += limite) {
    const lote = items.slice(i, i + limite);
    const lotResultados = await Promise.all(lote.map(fn));
    resultados.push(...lotResultados);
    console.log(`Lote ${Math.floor(i / limite) + 1} completado`);
  }
  return resultados;
}

Retry con async/await

async function conReintentos(fn, maxIntentos = 3, delay = 1000) {
  for (let intento = 1; intento <= maxIntentos; intento++) {
    try {
      return await fn();
    } catch (error) {
      console.warn(`Intento ${intento}/${maxIntentos} falló: ${error.message}`);
      if (intento === maxIntentos) throw error;
      await new Promise(r => setTimeout(r, delay * intento));
    }
  }
}

// Uso
try {
  const datos = await conReintentos(
    () => fetch("https://api.ejemplo.com/datos").then(r => r.json()),
    3, 1000
  );
  console.log("Datos:", datos);
} catch (error) {
  console.error("Todos los intentos fallaron");
}

Top-level await (módulos ES)

// En módulos ES (type="module"), podés usar await sin async
// <script type="module">
const respuesta = await fetch("/api/config");
const config = await respuesta.json();
console.log("Config cargada:", config);

Errores comunes de principiantes
  1. Usar await fuera de una función async: await solo funciona dentro de funciones async (excepto en módulos con top-level await).

  2. Poner await en un forEach: forEach no espera Promises. Usá for...of para iteración secuencial.

  3. No paralelizar cuando es posible: Si dos operaciones son independientes, usalas con Promise.all en vez de dos awaits secuenciales.

  4. Olvidar el try/catch: Sin manejo de errores, una Promise rechazada causa un "Unhandled Promise Rejection".

  5. Crear Promises innecesarias en funciones async: async function f() { return await valor; } → el await es redundante, basta con return valor.


Puntos clave de esta lección
  1. async hace que una función siempre retorne una Promise.
  2. await pausa la ejecución hasta que la Promise se resuelva — solo funciona dentro de async.
  3. Usá try/catch para manejar errores en funciones async.
  4. Para operaciones independientes, usá Promise.all() para ejecutar en paralelo.
  5. Para iteración secuencial de operaciones async, usá for...of (no forEach).
  6. Retry pattern con async/await es limpio y fácil de implementar.
  7. async/await es azúcar sintáctica sobre Promises — entender Promises sigue siendo fundamental.

Quiz de autoevaluación

1. ¿Qué retorna una función async?
a) Un valor normal
b) undefined
c) Siempre una Promise
d) Un callback

2. ¿Dónde se puede usar await?
a) En cualquier función
b) Solo dentro de funciones async (o top-level en módulos)
c) Solo en el navegador
d) Solo con fetch

3. ¿Cómo ejecutarías 3 fetches en paralelo con async/await?
a) Tres awaits consecutivos
b) await Promise.all([fetch1(), fetch2(), fetch3()])
c) await fetch1() && fetch2() && fetch3()
d) No se puede

4. ¿Qué pasa si un await falla y no hay try/catch?
a) Se ignora el error
b) Se produce un Unhandled Promise Rejection
c) La función retorna null
d) El programa se cierra

5. ¿Por qué no funciona await dentro de forEach?
a) Es un bug de JavaScript
b) forEach no está diseñado para funciones async; no espera las Promises
c) Solo funciona con map
d) forEach no acepta funciones

Respuestas: 1-c, 2-b, 3-b, 4-b, 5-b


💡 Concepto Clave

Revisemos los puntos más importantes de esta lección antes de continuar.

Ejercicio práctico

Misión: Agregador de noticias asíncrono

Creá un sistema que simule obtener noticias de múltiples fuentes:

  1. Simulá 3 APIs de noticias con delays aleatorios (y una que a veces falla)
  2. Usá Promise.allSettled para obtener noticias de todas las fuentes
  3. Combiná y ordená las noticias por fecha
  4. Implementá caché para no repetir llamadas
  5. Agregá retry para fuentes que fallan
  6. Mostrá un indicador de carga y el resultado final
const fuentes = [
  { nombre: "TechNews", url: "/api/tech", fiabilidad: 0.9 },
  { nombre: "WorldDaily", url: "/api/world", fiabilidad: 0.7 },
  { nombre: "LocalPress", url: "/api/local", fiabilidad: 0.95 },
];

async function obtenerTodasLasNoticias() {
  // Implementá acá
}
🧠 Pon a prueba tu conocimiento
¿Cuál es el aspecto más importante que aprendiste en esta lección?
  • Comprendo el concepto principal y puedo explicarlo con mis palabras
  • Entiendo cómo aplicarlo en mi situación específica
  • Necesito repasar algunas partes antes de continuar
  • Quiero ver más ejemplos prácticos del tema
✅ ¡Excelente! Continúa con la siguiente lección para profundizar más.