async/await en JavaScript: código asíncrono que parece síncrono

async/await es la sintaxis para trabajar con Promises introducida en ES2017. No reemplaza a las Promises: las envuelve en una sintaxis más parecida al código síncrono, lo que hace que el código asíncrono sea mucho más legible y fácil de depurar. Comprender que async/await es azúcar sobre Promises es fundamental para entender su comportamiento.

async function y await

Una función marcada con async siempre devuelve una Promise. await pausa la ejecución de la función async hasta que la Promise se resuelve, sin bloquear el hilo principal:

// Con Promises:
function obtenerUsuario(id) {
  return fetch(`/api/usuarios/${id}`)
    .then(r => {
      if (!r.ok) throw new Error('Error HTTP: ' + r.status);
      return r.json();
    })
    .then(datos => datos.usuario);
}

// Con async/await (equivalente, más legible):
async function obtenerUsuario(id) {
  const r = await fetch(`/api/usuarios/${id}`);
  if (!r.ok) throw new Error('Error HTTP: ' + r.status);
  const datos = await r.json();
  return datos.usuario;
}

// Consumir una función async:
obtenerUsuario(1).then(u => console.log(u));

// O con await (dentro de otra función async):
async function main() {
  const usuario = await obtenerUsuario(1);
  console.log(usuario);
}

Manejo de errores con try/catch

Con async/await, los errores de Promises rechazadas se capturan con try/catch, igual que los errores síncronos:

async function cargarDatos(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 (err) {
    // Captura tanto errores de red como el throw manual
    console.error('Error al cargar:', err.message);
    throw err;  // Re-lanzar para que el llamador pueda manejarlo
  } finally {
    console.log('Petición terminada');
  }
}

// Manejo en el punto de uso:
async function inicializar() {
  try {
    const config = await cargarDatos('/api/config');
    const usuarios = await cargarDatos('/api/usuarios');
    console.log('Inicializado con', usuarios.length, 'usuarios');
  } catch (err) {
    console.error('No se pudo inicializar:', err.message);
    mostrarPantallaError();
  }
}

await en bucles: cuidado con la serialización

Usar await dentro de un bucle for serializa las operaciones: cada una espera a que la anterior termine. Esto puede ser muy lento si las operaciones son independientes:

// MAL: secuencial (lento si las peticiones son independientes)
async function cargarTodosSecuencial(ids) {
  const resultados = [];
  for (const id of ids) {
    const usuario = await obtenerUsuario(id);  // Espera cada vez
    resultados.push(usuario);
  }
  return resultados;
}
// Con 10 peticiones de 200ms cada una: ~2000ms

// BIEN: paralelo con Promise.all
async function cargarTodosParalelo(ids) {
  const promesas = ids.map(id => obtenerUsuario(id));
  return await Promise.all(promesas);
}
// Con 10 peticiones de 200ms cada una: ~200ms

// Cuándo el bucle secuencial SÍ tiene sentido:
// Cuando cada petición depende del resultado de la anterior
async function paginacionSecuencial() {
  let pagina = 1;
  let hayMas = true;
  const todos = [];

  while (hayMas) {
    const { datos, siguiente } = await fetch(`/api?page=${pagina}`).then(r => r.json());
    todos.push(...datos);
    hayMas = !!siguiente;
    pagina++;
  }
  return todos;
}

Top-level await

En módulos ES (archivos con type="module"), await puede usarse directamente en el nivel superior, sin necesidad de envolver en una función async:

// En un módulo ES (top-level await)
const config = await fetch('/api/config').then(r => r.json());
const { tema, idioma } = config;

export { tema, idioma };

// Los módulos que importen este esperarán a que el await termine

async/await con Promise.all y Promise.allSettled

async/await y los combinadores de Promises funcionan perfectamente juntos:

async function cargaCompleta(usuarioId) {
  // Lanzar todas las peticiones en paralelo
  const [usuario, pedidos, preferencias] = await Promise.all([
    obtenerUsuario(usuarioId),
    obtenerPedidos(usuarioId),
    obtenerPreferencias(usuarioId)
  ]);

  return { usuario, pedidos, preferencias };
}

// Con allSettled para no fallar si algo opcional no carga:
async function cargaRobusta(usuarioId) {
  const resultados = await Promise.allSettled([
    obtenerUsuario(usuarioId),
    obtenerPedidos(usuarioId),      // puede fallar
    obtenerRecomendaciones(usuarioId) // puede fallar
  ]);

  const [resUsuario, resPedidos, resReco] = resultados;

  return {
    usuario: resUsuario.status === 'fulfilled' ? resUsuario.value : null,
    pedidos: resPedidos.status === 'fulfilled' ? resPedidos.value : [],
    recomendaciones: resReco.status === 'fulfilled' ? resReco.value : []
  };
}

El error más frecuente con async/await es olvidar que await solo puede usarse dentro de funciones async. Fuera de ellas, necesitas .then() o top-level await en módulos ES. El segundo error más frecuente es no poner await donde hace falta y recibir una Promise en lugar del valor esperado.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP