Kodokon kodokon.com

Asíncrono en profundidad: promesas, async/await, manejo de errores

Aprovecha el bucle de eventos, paraleliza tus promesas correctamente y haz fiable el manejo de errores asíncronos.

11 min · 3 preguntas

Abrir esta lección en Kodokon

El modelo de ejecución se apoya en una cola de macrotareas (setTimeout, E/S) y una cola de microtareas (.then, await, queueMicrotask). Tras cada tarea, el motor vacía la cola de microtareas por completo antes de continuar. Consecuencia directa: una promesa resuelta siempre se ejecuta antes que un setTimeout(fn, 0), y una cadena de microtareas demasiado larga puede congelar el renderizado con la misma seguridad que un bucle síncrono.

JAVASCRIPT
console.log("start");
setTimeout(() => console.log("macrotask"), 0);
Promise.resolve().then(() => console.log("microtask"));
console.log("end");
// start, end, microtask, macrotask
Las microtareas se ejecutan antes que cualquier macrotarea pendiente.

async/await es azúcar sintáctico sobre las promesas, pero esconde una trampa de rendimiento: cada await dentro de un bucle serializa las operaciones. Si las peticiones son independientes, inícialas todas primero y luego espera el lote con Promise.all. Para tres peticiones de 100 ms, la versión secuencial cuesta 300 ms; la versión paralela, 100 ms.

JAVASCRIPT
const fetchUser = (id) => new Promise((res) => {
  setTimeout(() => res({ id }), 100);
});
async function loadSequential(ids) {
  const users = [];
  for (const id of ids) {
    users.push(await fetchUser(id)); // ~100 ms each
  }
  return users;
}
async function loadParallel(ids) {
  return Promise.all(ids.map(fetchUser)); // ~100 ms total
}
Mismo resultado, latencia dividida por el número de llamadas.

Elige el combinador adecuado para el contrato de fallo que necesitas: Promise.all se rechaza ante el primer fallo (todo o nada), allSettled espera a todos y describe cada resultado, race toma la primera resolución (éxito o fallo, útil para un tiempo de espera), any toma el primer éxito y solo se rechaza si todo falla. Para un panel que debe mostrar resultados parciales, allSettled es casi siempre la elección correcta.

JAVASCRIPT
const tasks = [
  Promise.resolve("profile loaded"),
  Promise.reject(new Error("network unavailable")),
];
const results = await Promise.allSettled(tasks);
for (const r of results) {
  if (r.status === "fulfilled") {
    console.log("success:", r.value);
  } else {
    console.error("failure:", r.reason.message);
  }
}
Cada resultado se describe; ningún fallo oculta a los demás.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. ¿En qué orden se ejecutan un console.log síncrono, un callback de Promise.resolve().then y un callback de setTimeout(fn, 0)?
    • síncrono, setTimeout, then
    • síncrono, then, setTimeout
    • then, síncrono, setTimeout
  2. Lanzas diez peticiones independientes. ¿Qué enfoque minimiza la latencia total?
    • Un await por petición dentro de un bucle for...of
    • Crear todas las promesas y luego esperar Promise.all
    • Encadenar las llamadas .then una tras otra
    • Usar setTimeout para espaciar las llamadas
  3. ¿Por qué try { doAsync(); } catch (e) {} sin await no logra capturar el rechazo de doAsync?
    • Porque catch no funciona dentro de funciones async
    • Porque el rechazo ocurre después de que el bloque try haya salido, ya que la promesa nunca se espera
    • Porque las promesas solo se pueden capturar con .catch