Las Promises son el mecanismo de JavaScript para trabajar con operaciones asíncronas de forma estructurada, evitando el callback hell que hacía el código difícil de leer y mantener. Una Promise representa un valor que puede estar disponible ahora, en el futuro o nunca, y proporciona métodos para reaccionar a cada posibilidad.
Estados de una Promise
Una Promise siempre está en uno de tres estados: pending (en curso), fulfilled (resuelta con un valor) o rejected (rechazada con un motivo). Una vez que sale de pending, el estado es definitivo y no cambia:
// Crear una Promise manualmente
const promesa = new Promise((resolve, reject) => {
// Ejecutor: se llama inmediatamente y de forma síncrona
const exito = Math.random() > 0.5;
if (exito) {
resolve('Datos obtenidos'); // fulfilled
} else {
reject(new Error('Fallo en la red')); // rejected
}
});
// La Promise ya fue creada y está en estado pending (o ya resuelta)
console.log(promesa); // Promise { } o Promise { 'Datos obtenidos' }
then(), catch() y finally()
then(onFulfilled, onRejected) maneja el resultado. catch(fn) es azúcar para then(undefined, fn). finally(fn) se ejecuta siempre, sin importar el resultado:
function obtenerUsuario(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, nombre: 'Ana', email: '[email protected]' });
} else {
reject(new Error('ID inválido'));
}
}, 500);
});
}
obtenerUsuario(1)
.then(usuario => {
console.log('Usuario:', usuario.nombre); // 'Ana'
return usuario.email; // El return encadena el siguiente then
})
.then(email => {
console.log('Email:', email); // '[email protected]'
})
.catch(err => {
console.error('Error:', err.message);
})
.finally(() => {
console.log('Petición terminada'); // Siempre se ejecuta
});
Encadenar Promises: el return es clave
Cada then() devuelve una nueva Promise. Para encadenar correctamente, el callback de then() debe devolver el valor o la Promise del paso siguiente. Olvidar el return es uno de los errores más frecuentes:
// MAL: sin return, la cadena se rompe
obtenerUsuario(1)
.then(usuario => {
obtenerPedidos(usuario.id); // Falta return: esta Promise se pierde
})
.then(pedidos => {
console.log(pedidos); // undefined, porque el then anterior no devolvió nada
});
// BIEN: con return
obtenerUsuario(1)
.then(usuario => {
return obtenerPedidos(usuario.id); // Devuelve la Promise
})
.then(pedidos => {
console.log(pedidos); // Los pedidos reales
})
.catch(err => {
// Un solo catch captura errores de cualquier punto de la cadena
console.error(err);
});
Promise.all, allSettled, race y any
Cuando necesitas coordinar múltiples promises en paralelo:
const p1 = fetch('/api/usuario/1').then(r => r.json());
const p2 = fetch('/api/pedidos').then(r => r.json());
const p3 = fetch('/api/productos').then(r => r.json());
// Promise.all: espera a todas; falla si alguna falla
Promise.all([p1, p2, p3])
.then(([usuario, pedidos, productos]) => {
console.log('Todo listo:', usuario, pedidos, productos);
})
.catch(err => {
// Si cualquiera de las tres falla, llega aquí
console.error('Alguna petición falló:', err);
});
// Promise.allSettled: espera a todas, nunca falla
Promise.allSettled([p1, p2, p3])
.then(resultados => {
resultados.forEach((r, i) => {
if (r.status === 'fulfilled') {
console.log(`Petición ${i}: OK`, r.value);
} else {
console.error(`Petición ${i}: Error`, r.reason);
}
});
});
// Promise.race: la primera que resuelva (o rechace) gana
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 3000)
);
Promise.race([fetch('/api/datos'), timeout])
.then(r => r.json())
.catch(err => console.error(err.message));
// Promise.any: la primera que resuelva; falla solo si todas fallan
Promise.any([fetch('/cdn1/img.jpg'), fetch('/cdn2/img.jpg'), fetch('/cdn3/img.jpg')])
.then(r => console.log('Imagen cargada desde', r.url))
.catch(() => console.error('Todos los CDN fallaron'));
Crear Promises ya resueltas
Promise.resolve() y Promise.reject() crean Promises ya en estado final. Son útiles para normalizar valores síncronos en APIs que esperan Promises:
// Normalizar: puede ser valor síncrono o Promise
function obtenerConfig(cache) {
if (cache) {
return Promise.resolve(cache); // Devuelve Promise resuelta
}
return fetch('/api/config').then(r => r.json());
}
// Promise.reject para propagar errores:
function validar(datos) {
if (!datos.nombre) {
return Promise.reject(new Error('Nombre requerido'));
}
return Promise.resolve(datos);
}
La transición natural al dominar Promises es pasar a async/await, que es azúcar sintáctico sobre este mismo mecanismo. Comprender cómo funcionan las Promises internamente hace que sea mucho más fácil depurar código async/await cuando algo sale mal.
