Inmersión Profunda en Promesas
Internos de las promesas, encadenamiento, manejo de errores, Promise.all, Promise.race y más
Comprender las Promesas
Una Promesa representa la eventual finalización (o fallo) de una operación asíncrona y su valor resultante. Las promesas proporcionan una alternativa más limpia a los callbacks para manejar código asíncrono.
Estados de las Promesas
- Pendiente (Pending) — Estado inicial, ni cumplida ni rechazada
- Cumplida (Fulfilled) — Operación completada exitosamente
- Rechazada (Rejected) — Operación falló
- Resuelta (Settled) — Cumplida o rechazada (estado final)
Creando Promesas
// Creación básica de Promesa
const promesa = new Promise((resolve, reject) => {
// Operación asíncrona
setTimeout(() => {
const exito = true;
if (exito) {
resolve("¡Operación exitosa!"); // Cumplir
} else {
reject(new Error("Operación falló")); // Rechazar
}
}, 1000);
});
// Consumiendo la promesa
promesa
.then(resultado => console.log(resultado)) // "¡Operación exitosa!"
.catch(error => console.error(error));
// Atajo para promesas resueltas/rechazadas
Promise.resolve("valor inmediato");
Promise.reject(new Error("error inmediato"));
Encadenamiento de Promesas
.then() siempre retorna una nueva Promesa, permitiendo encadenamiento:
fetch("/api/user")
.then(response => response.json()) // Retorna Promesa
.then(user => fetch(`/api/posts/${user.id}`)) // Retorna Promesa
.then(response => response.json())
.then(posts => {
console.log("Posts:", posts);
return posts.length;
})
.then(count => console.log(`Encontrados ${count} posts`))
.catch(error => console.error("Error:", error));
// Cada .then() recibe el valor del anterior
// Retornar una Promesa espera a que se resuelva
Manejo de Errores
// .catch() maneja errores de cualquier punto en la cadena
fetchUser()
.then(user => fetchPosts(user.id))
.then(posts => displayPosts(posts))
.catch(error => {
// Captura errores de fetchUser, fetchPosts o displayPosts
console.error("Algo salió mal:", error);
});
// Múltiples bloques catch para manejo específico
fetchUser()
.then(user => {
if (!user.isActive) {
throw new Error("Usuario inactivo");
}
return fetchPosts(user.id);
})
.catch(error => {
if (error.message === "Usuario inactivo") {
return []; // Retornar array vacío, cadena continúa
}
throw error; // Re-lanzar otros errores
})
.then(posts => console.log(posts));
// .finally() se ejecuta independientemente de éxito/fallo
fetchData()
.then(data => process(data))
.catch(error => handleError(error))
.finally(() => {
hideLoadingSpinner(); // Siempre se ejecuta
});
Promise.all()
Esperar a que múltiples promesas se resuelvan. Se rechaza si alguna promesa se rechaza:
const promesa1 = fetch("/api/users");
const promesa2 = fetch("/api/posts");
const promesa3 = fetch("/api/comments");
Promise.all([promesa1, promesa2, promesa3])
.then(([users, posts, comments]) => {
// Las tres resueltas
console.log("Usuarios:", users);
console.log("Posts:", posts);
console.log("Comentarios:", comments);
})
.catch(error => {
// El primer rechazo falla todo
console.error("Una petición falló:", error);
});
// Ejemplo práctico: Llamadas API en paralelo
async function cargarDashboard() {
const [user, notifications, stats] = await Promise.all([
fetchUser(),
fetchNotifications(),
fetchStats()
]);
return { user, notifications, stats };
}
Promise.allSettled()
Esperar a que todas las promesas se resuelvan, independientemente de éxito/fallo:
const promesas = [
Promise.resolve("Éxito"),
Promise.reject("Error"),
Promise.resolve("Otro éxito")
];
Promise.allSettled(promesas)
.then(resultados => {
resultados.forEach((resultado, indice) => {
if (resultado.status === "fulfilled") {
console.log(`Promesa ${indice}: ${resultado.value}`);
} else {
console.log(`Promesa ${indice} falló: ${resultado.reason}`);
}
});
});
// Salida:
// Promesa 0: Éxito
// Promesa 1 falló: Error
// Promesa 2: Otro éxito
Promise.race()
Retorna cuando la primera promesa se resuelve (cumple o rechaza):
// Patrón de timeout
function fetchConTimeout(url, timeout = 5000) {
const fetchPromise = fetch(url);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Timeout")), timeout);
});
return Promise.race([fetchPromise, timeoutPromise]);
}
fetchConTimeout("/api/slow-endpoint", 3000)
.then(response => response.json())
.catch(error => console.error(error)); // "Timeout" si > 3s
Promise.any()
Retorna cuando la primera promesa se cumple. Solo se rechaza si todas se rechazan:
// Probar múltiples fuentes, usar primer éxito
const mirrors = [
fetch("https://mirror1.com/data"),
fetch("https://mirror2.com/data"),
fetch("https://mirror3.com/data")
];
Promise.any(mirrors)
.then(response => {
console.log("Obtenidos datos del mirror más rápido");
return response.json();
})
.catch(error => {
// AggregateError si TODOS los mirrors fallaron
console.error("Todos los mirrors fallaron:", error.errors);
});
Creando Utilidades de Promesas
// Función de retraso
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
await delay(1000); // Esperar 1 segundo
// Reintentar con backoff exponencial
async function fetchConReintento(url, reintentos = 3) {
for (let i = 0; i < reintentos; i++) {
try {
return await fetch(url);
} catch (error) {
if (i === reintentos - 1) throw error;
await delay(Math.pow(2, i) * 1000); // 1s, 2s, 4s
}
}
}
// Ejecución secuencial
async function secuencial(tareas) {
const resultados = [];
for (const tarea of tareas) {
resultados.push(await tarea());
}
return resultados;
}
Patrones Comunes
// Promisificar función basada en callbacks
function promisify(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
fn(...args, (error, resultado) => {
if (error) reject(error);
else resolve(resultado);
});
});
};
}
const readFileAsync = promisify(fs.readFile);
const data = await readFileAsync("file.txt", "utf8");
// Promesa memoizada (cachear resultado)
function memoizedFetch(url) {
const cache = new Map();
return function() {
if (!cache.has(url)) {
cache.set(url, fetch(url).then(r => r.json()));
}
return cache.get(url);
};
}
⚠️ Errores Comunes
- Olvidar retornar: Siempre retorna promesas en cadenas .then()
- Anidar en lugar de encadenar: Evita callback hell en promesas
- Rechazos no manejados: Siempre ten un .catch() o try/catch
- Crear promesas innecesarias: No envuelvas fetch() en new Promise()
💡 Puntos Clave
- • Las promesas representan valores eventuales y tienen tres estados
- • Encadena con .then(), maneja errores con .catch(), limpia con .finally()
- • Usa Promise.all() para operaciones paralelas, Promise.race() para timeouts
- • Promise.allSettled() cuando necesitas todos los resultados independientemente de fallos
- • Siempre maneja rechazos para evitar advertencias de promesas rechazadas no manejadas
Práctica + protecciones de producción
- • Reescribe una API basada en callbacks con Promesas, luego agrega cancelación con
AbortController. - • Construye un helper de reintentos con jitter y máximo de intentos; registra qué intentos tuvieron éxito.
- • Agrega
Promise.anya un fetch espejado y mide mejoras de latencia percibida. - • Activa listeners de
unhandledrejectionen dev para mostrar rutas.catchperdidas.