</> HTML5Advent
ENFRESDEITPT

// js · Web Platform Advent #3

async/await en JavaScript: guía práctica

Cómo funcionan async y await, su relación con las promesas, el manejo de errores con try/catch, await en bucles y el error de serie-vs-paralelo — con ejemplos ejecutables.

Las manos de un desarrollador escribiendo en un teclado con código fuente de colores en el monitor

Las palabras clave async y await son sintaxis sobre las promesas. Te permiten escribir código asíncrono que se lee de arriba abajo como código síncrono, sin dejar de ser no bloqueante por debajo. Si conoces las promesas, ya conoces async/await — solo elimina la ceremonia de los .then().

Lo básico

Marca una función como async y podrás usar await dentro de ella. await pausa esa función hasta que la promesa se resuelve y te da el valor resuelto:

async function getUser(id) {
  const res = await fetch('/api/user/' + id);
  const user = await res.json();
  return user.name;
}

getUser(42).then((name) => console.log(name));

Dos cosas que recordar: una función async siempre devuelve una promesa (lo que devuelvas se envuelve en una), y await solo funciona dentro de una función async — salvo en el nivel superior de un módulo ES, donde se permite el top-level await.

async/await vs promesas

Es la misma maquinaria. Esta cadena de promesas…

fetch('/api/user')
  .then((res) => res.json())
  .then((user) => console.log(user.name));

…es exactamente equivalente a esta versión async, que a la mayoría le resulta más fácil de leer y depurar:

const res = await fetch('/api/user');
const user = await res.json();
console.log(user.name);

Puedes mezclarlas libremente — await cualquier promesa, incluido el resultado de Promise.all().

Manejo de errores con try/catch

Con async/await capturas las promesas rechazadas con un try/catch normal, la misma construcción que usas para los errores síncronos:

async function loadUser(id) {
  try {
    const res = await fetch('/api/user/' + id);
    if (!res.ok) throw new Error('HTTP ' + res.status);
    return await res.json();
  } catch (err) {
    console.error('No se pudo cargar el usuario:', err.message);
    return null;
  } finally {
    console.log('loadUser terminado');
  }
}

Un await rechazado lanza una excepción en esa línea, así que se ejecuta el bloque catch. El bloque finally se ejecuta haya o no habido un error.

Una autopista de varios carriles con coches y camiones circulando uno al lado del otro en paralelo
Las tareas independientes pueden ejecutarse una al lado de otra como vehículos en carriles paralelos — esperarlas de una en una impone en cambio una cola en fila india.

El gran error: await en serie vs en paralelo

El fallo más común con async/await es esperar tareas independientes una tras otra. Esto se ejecuta en serie — la segunda petición no empieza hasta que termina la primera:

// Lento: tiempo total = a + b + c
const a = await fetchA();
const b = await fetchB();
const c = await fetchC();

Si las tareas no dependen entre sí, lánzalas todas primero y luego espera juntas con Promise.all. Ahora se ejecutan en paralelo:

// Rápido: tiempo total ≈ la más lenta
const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);

await en bucles

Un bucle for normal con await procesa los elementos de uno en uno, lo cual es correcto cuando cada paso depende del anterior o debes evitar saturar un servidor:

for (const id of ids) {
  const item = await fetchItem(id);   // secuencial — uno tras otro
  console.log(item);
}

Para procesarlos en paralelo, transforma en un array de promesas y await Promise.all. Ojo: forEach no espera a los callbacks async — usa map + Promise.all en su lugar:

const items = await Promise.all(ids.map((id) => fetchItem(id)));

Referencia rápida

ObjetivoPatrón
Esperar una promesaconst x = await p;
Manejar errorestry { await ... } catch (e) { ... }
Tareas independientes en paraleloawait Promise.all([...])
Bucle secuencialfor...of con await
Bucle concurrentemapPromise.all

async/await no reemplaza a las promesas — las hace legibles. Mantén Promise.all a mano para el paralelismo, recurre a try/catch para los errores y vigila el await en serie accidental, y tendrás el JavaScript asíncrono bajo control.