Async/await avanzado en JavaScript: patrones paralelo/secuencial, error handling y cancelación

async/await hace que el código asíncrono parezca síncrono, pero escribirlo en serie cuando podría ir en paralelo es uno de los errores más frecuentes. Este artículo cubre los patrones que van más allá del caso básico: cómo paralelizar correctamente, manejar errores sin verbosidad excesiva, iterar flujos y cancelar operaciones.

El antipatrón de await en serie

El error más común es hacer await de promesas de forma secuencial cuando no hay dependencia entre ellas. Cada petición espera a que termine la anterior, multiplicando el tiempo de espera innecesariamente.

// Antipatrón: espera 300ms + 200ms + 400ms = 900ms
const usuario = await fetchUsuario(id);
const pedidos = await fetchPedidos(id);
const config = await fetchConfig();

// Correcto: espera solo max(300, 200, 400) = 400ms
const [usuario, pedidos, config] = await Promise.all([
  fetchUsuario(id),
  fetchPedidos(id),
  fetchConfig(),
]);

La regla es clara: si el segundo await no necesita el resultado del primero, ambos deben ir dentro de un Promise.all.

Manejo de errores: try/catch vs helper seguro

Los bloques try/catch anidados ensuchan el código. Una alternativa es un helper que convierte promesas en tuplas [error, resultado] al estilo Go, sin excepciones:

const seguros = async (promesa) =>
  promesa.then(v => [null, v]).catch(e => [e, null]);

// Uso limpio sin try/catch
const [err, datos] = await seguros(fetch('/api/datos').then(r => r.json()));
if (err) {
  console.error('Error al cargar datos:', err.message);
  return;
}
console.log(datos);

Combinado con Promise.all:

const resultados = await Promise.all([
  seguros(fetchUsuario(id)),
  seguros(fetchPedidos(id)),
]);
// resultados[0] = [null, usuario] o [Error, null]
// resultados[1] = [null, pedidos] o [Error, null]

for await...of para iterar streams

for await...of permite iterar objetos asíncronos iterables línea a línea o fragmento a fragmento sin acumular todo en memoria. Disponible en cualquier contexto async, incluyendo top-level await en módulos.

async function procesarCSVGrande(url) {
  const res = await fetch(url);
  const reader = res.body.getReader();
  const decoder = new TextDecoder();

  // Adaptador de ReadableStream a iterable asíncrono
  const stream = {
    [Symbol.asyncIterator]() {
      return {
        async next() {
          const { done, value } = await reader.read();
          return done
            ? { done: true }
            : { done: false, value: decoder.decode(value) };
        },
      };
    },
  };

  for await (const fragmento of stream) {
    procesarFragmento(fragmento);
  }
}

Cancelación con AbortController

AbortController permite cancelar peticiones fetch (y otras APIs que acepten una señal) cuando el componente se desmonta, el usuario navega o expira un timeout:

async function buscarConCancelacion(termino) {
  const controlador = new AbortController();

  // Cancelar tras 5 segundos
  const timer = setTimeout(() => controlador.abort(), 5000);

  try {
    const res = await fetch(`/api/buscar?q=${termino}`, {
      signal: controlador.signal,
    });
    clearTimeout(timer);
    return await res.json();
  } catch (err) {
    if (err.name === 'AbortError') {
      console.log('Búsqueda cancelada');
    } else {
      throw err;
    }
  }
}

En React puedes devolver la función abort desde el useEffect para cancelar automáticamente al desmontar el componente.

Top-level await en módulos ESM

Desde ES2022, los módulos ES pueden usar await directamente en el nivel raíz sin envolver en una función async. Esto es útil para cargar configuración o inicializar conexiones antes de exportar:

// config.js
const res = await fetch('/api/config');
export const config = await res.json();

// main.js
import { config } from './config.js';
// config ya está resuelto cuando se importa
console.log(config.apiUrl);

El módulo que use top-level await bloquea la evaluación de los módulos que lo importen, así que úsalo solo cuando la inicialización sea genuinamente necesaria antes de cualquier exportación.

COMPARTE ESTE ARTÍCULO

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