Patrones Async/Await
Uso avanzado de async/await, manejo de errores, ejecución paralela y mejores prácticas
Fundamentos de Async/Await
async/await es azúcar sintáctico sobre Promesas que hace que el código asíncrono se vea y
comporte más como código síncrono. Es la forma preferida de escribir JavaScript asíncrono en aplicaciones modernas.
Puntos Clave
- async — Declara una función que retorna una Promesa
- await — Pausa la ejecución hasta que la Promesa se resuelve
- Valor de retorno — Automáticamente envuelto en Promise.resolve()
- Errores — Los errores lanzados se convierten en promesas rechazadas
Sintaxis Básica
// Declaración de función async
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
return user;
}
// Función flecha
const fetchUser = async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
};
// Usando la función
fetchUser(1)
.then(user => console.log(user))
.catch(error => console.error(error));
// O con await (dentro de otra función async)
const user = await fetchUser(1);
Manejo de Errores con try/catch
async function fetchData() {
try {
const response = await fetch("/api/data");
if (!response.ok) {
throw new Error(`Error HTTP: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Fetch falló:", error.message);
throw error; // Re-lanzar si es necesario
} finally {
console.log("Código de limpieza se ejecuta siempre");
}
}
// Múltiples operaciones en un bloque try
async function processUser(id) {
try {
const user = await fetchUser(id);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
return { user, posts, comments };
} catch (error) {
// Captura errores de cualquiera de los tres fetches
console.error("Error en pipeline:", error);
return null;
}
}
Ejecución Paralela
Esperar múltiples promesas simultáneamente para mejor rendimiento:
// ❌ Secuencial (lento) - cada uno espera al anterior
async function secuencial() {
const users = await fetchUsers(); // Espera 1s
const posts = await fetchPosts(); // Espera 1s
const comments = await fetchComments(); // Espera 1s
// Total: ~3s
}
// ✅ Paralelo (rápido) - todos ejecutan al mismo tiempo
async function paralelo() {
const [users, posts, comments] = await Promise.all([
fetchUsers(), // Inicia inmediatamente
fetchPosts(), // Inicia inmediatamente
fetchComments() // Inicia inmediatamente
]);
// Total: ~1s (la petición más lenta)
}
// ✅ Paralelo con manejo de errores para cada uno
async function paraleloConRespaldos() {
const resultados = await Promise.allSettled([
fetchUsers(),
fetchPosts(),
fetchComments()
]);
return resultados.map(r =>
r.status === "fulfilled" ? r.value : null
);
}
Decisión Secuencial vs Paralelo
// Usa SECUENCIAL cuando las operaciones dependen entre sí
async function dependiente() {
const user = await fetchUser(1);
const posts = await fetchPosts(user.id); // Necesita user.id
const comments = await fetchComments(posts); // Necesita posts
return { user, posts, comments };
}
// Usa PARALELO cuando las operaciones son independientes
async function independiente() {
const [user, settings, notifications] = await Promise.all([
fetchUser(), // Independiente
fetchSettings(), // Independiente
fetchNotifications() // Independiente
]);
return { user, settings, notifications };
}
// HÍBRIDO: Mezcla ambos enfoques
async function hibrido(userId) {
const user = await fetchUser(userId);
// Estos dependen de user pero no entre sí
const [posts, followers, settings] = await Promise.all([
fetchPosts(user.id),
fetchFollowers(user.id),
fetchSettings(user.id)
]);
return { user, posts, followers, settings };
}
Bucles con Async/Await
// Bucle secuencial - uno a la vez
async function procesarSecuencialmente(ids) {
const resultados = [];
for (const id of ids) {
const resultado = await processItem(id);
resultados.push(resultado);
}
return resultados;
}
// Bucle paralelo - todos a la vez
async function procesarEnParalelo(ids) {
const promesas = ids.map(id => processItem(id));
return Promise.all(promesas);
}
// Concurrencia controlada - procesamiento por lotes
async function procesarPorLotes(ids, tamanoLote = 5) {
const resultados = [];
for (let i = 0; i < ids.length; i += tamanoLote) {
const lote = ids.slice(i, i + tamanoLote);
const resultadosLote = await Promise.all(
lote.map(id => processItem(id))
);
resultados.push(...resultadosLote);
}
return resultados;
}
// ⚠️ forEach no funciona con await
// ❌ MALO - no esperará las promesas
ids.forEach(async (id) => {
await processItem(id); // ¡No espera!
});
// ✅ BUENO - usa for...of
for (const id of ids) {
await processItem(id);
}
Patrones Avanzados
// Patrón de reintento
async function fetchConReintento(url, maxReintentos = 3) {
for (let intento = 1; intento <= maxReintentos; intento++) {
try {
return await fetch(url);
} catch (error) {
if (intento === maxReintentos) throw error;
const espera = Math.pow(2, intento) * 1000;
console.log(`Reintento ${intento} en ${espera}ms`);
await new Promise(r => setTimeout(r, espera));
}
}
}
// Patrón de timeout
async function fetchConTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal: controller.signal });
return response;
} finally {
clearTimeout(timeoutId);
}
}
// Operación async cancelable
function crearPeticionCancelable(url) {
const controller = new AbortController();
const promesa = fetch(url, { signal: controller.signal });
return {
promesa,
cancelar: () => controller.abort()
};
}
const { promesa, cancelar } = crearPeticionCancelable("/api/data");
// Más tarde: cancelar();
Async en Métodos de Clase
class UserService {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
async getUser(id) {
const response = await fetch(`${this.baseUrl}/users/${id}`);
return response.json();
}
async createUser(userData) {
const response = await fetch(`${this.baseUrl}/users`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userData)
});
return response.json();
}
// Los getters no pueden ser async, pero pueden retornar una Promesa
get currentUser() {
return this.getUser("me");
}
}
const service = new UserService("/api");
const user = await service.getUser(1);
⚠️ Errores Comunes
- Secuencial cuando es posible paralelo: No uses await innecesariamente en secuencia
- Usar forEach con async: Usa for...of o Promise.all() en su lugar
- No manejar errores: Siempre usa try/catch o .catch()
- Olvidar await: Resulta en trabajar con objetos Promise, no valores
- await en función no-async: Solo funciona dentro de funciones async
💡 Puntos Clave
- • async/await hace que el código async sea legible y mantenible
- • Usa try/catch para manejo de errores
- • Usa Promise.all() para operaciones paralelas independientes
- • Usa for...of para bucles async secuenciales
- • Considera AbortController para cancelación y timeouts
- • Siempre maneja errores para prevenir rechazos no manejados
Consejos de flujo de trabajo
- • Decide la concurrencia por adelantado: marca pasos que pueden ejecutarse en paralelo y envuélvelos en
Promise.all. - • Siempre empareja fetches async con
AbortControllerpara cancelar peticiones obsoletas en navegación o cambio de input. - • Al refactorizar, comienza agregando un solo
awaity registrando tiempos para detectar serialización accidental. - • En tests, afirma en rutas de rechazo con
await expect(promise).rejects...para asegurar que los errores se manejan.