Patrones async/await en JavaScript que debes conocer
La programación asíncrona es una parte fundamental del JavaScript moderno, y async/await la ha hecho mucho más legible. Pero muchos desarrolladores aún luchan con errores comunes: rechazos de promesas no controlados, peticiones secuenciales lentas o manejo de errores confuso. En este artículo, exploraremos patrones prácticos de async/await que puedes aplicar de inmediato para escribir código más limpio y eficiente.
1. Sintaxis básica de async/await
Una función async siempre devuelve una Promise. La palabra clave await pausa la ejecución hasta que la Promise se resuelve, y luego devuelve su valor resuelto. Si la Promise se rechaza, lanza un error que puedes capturar con try/catch.
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch failed:', error);
}
}
Esta es la base. Pero los escenarios del mundo real a menudo requieren enfoques más matizados.
2. Manejo de errores con elegancia
Una de las mayores ventajas de async/await es que puedes usar try/catch tanto para errores síncronos como asíncronos. Sin embargo, ten cuidado con múltiples llamadas await en un solo bloque try: si la primera falla, las siguientes no se ejecutarán.
Para mayor granularidad, puedes envolver cada await en su propio try/catch o usar una función auxiliar para evitar bloques try/catch repetitivos.
async function getUser(id) {
try {
const user = await fetchUser(id);
return user;
} catch (error) {
console.error('Failed to fetch user:', error);
throw error;
}
}
3. Ejecutar promesas en paralelo
Esperar múltiples promesas independientes de forma secuencial es un error de rendimiento común. Si necesitas resultados de múltiples fuentes, usa Promise.all() para ejecutarlas de forma concurrente.
// Secuencial: lento
const user = await fetchUser();
const posts = await fetchPosts();
// Paralelo: rápido
const [user, posts] = await Promise.all([fetchUser(), fetchPosts()]);
Usa Promise.allSettled() cuando quieras esperar a que todas las promesas se resuelvan, independientemente de si se cumplen o se rechazan. Esto es útil cuando quieres recopilar resultados aunque algunas fallen.
4. Ejecución secuencial cuando el orden importa
A veces necesitas procesar elementos en orden, como cuando cada paso depende del anterior. En ese caso, usa un bucle for...of con await en lugar de forEach (que no espera a los callbacks asíncronos).
for (const item of items) {
await processItem(item);
}
Si necesitas limitar la concurrencia, considera librerías como p-limit o implementa una cola simple.
5. Evitar la trampa de async/await en bucles
Usar await dentro de Array.prototype.forEach es un error clásico: forEach no espera al callback asíncrono, por lo que el bucle termina antes de que las promesas se resuelvan. Usa siempre for...of o map con Promise.all.
6. Async/await con timeouts
Para evitar peticiones colgadas, puedes hacer una carrera entre una promesa y un timeout.
function timeout(ms) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
);
}
await Promise.race([fetchData(), timeout(5000)]);
Este patrón es útil para llamadas a API que podrían colgarse.
7. Generadores asíncronos y for await...of
Los generadores asíncronos te permiten producir un flujo de valores de forma asíncrona. Puedes consumirlos con for await...of.
async function* fetchPages() {
let page = 1;
while (true) {
const data = await fetch(`/api/items?page=${page}`);
if (data.length === 0) break;
yield data;
page++;
}
}
for await (const page of fetchPages()) {
console.log(page);
}
Esto es ideal para APIs paginadas o streaming de datos.
8. Errores comunes y cómo evitarlos
- Olvidar el await: Llamar a una función async sin await devuelve una Promise, y los errores pueden quedar sin manejar.
- Usar await en funciones no async: Error de sintaxis. Marca siempre la función como async.
- Abusar de awaits secuenciales: Usa Promise.all cuando sea posible.
- No manejar rechazos: Añade siempre .catch o try/catch.
9. Buenas prácticas para código async limpio
- Prefiere async/await sobre Promises crudas por legibilidad.
- Usa Promise.all para operaciones independientes.
- Maneja los errores en el nivel adecuado.
- Mantén las funciones async pequeñas y enfocadas.
- Usa async/await con precaución en bucles; elige el patrón correcto.
FAQ
¿Cuál es la diferencia entre async/await y Promises?
Async/await es azúcar sintáctico sobre Promises, haciendo que el código asíncrono parezca síncrono. Simplifica el encadenamiento y el manejo de errores con try/catch.
¿Puedo usar await dentro de un bucle forEach?
No, forEach no espera a los callbacks asíncronos. Usa for...of o Promise.all con map en su lugar.
¿Cómo ejecuto múltiples operaciones async en paralelo?
Usa Promise.all() para ejecutarlas de forma concurrente y esperar a que todas se resuelvan.
Cuando trabajes con APIs JSON, puedes formatear y validar respuestas rápidamente usando nuestro JSON Formatter.