Promesas en JavaScript Explicadas: Una Guía Completa
Las Promesas en JavaScript son una característica poderosa para manejar operaciones asíncronas. Proporcionan una forma más limpia e intuitiva de trabajar con código asíncrono en comparación con los enfoques tradicionales de callbacks. En esta guía completa, exploraremos todo lo que necesitas saber sobre las Promesas, desde conceptos básicos hasta patrones avanzados.
¿Qué es una Promesa?
Una Promesa es un objeto que representa la eventual finalización o fallo de una operación asíncrona. Esencialmente, es un objeto devuelto al que adjuntas callbacks, en lugar de pasar callbacks a una función.
Una Promesa existe en uno de tres estados:
- Pendiente: Estado inicial, ni cumplida ni rechazada.
- Cumplida: La operación se completó exitosamente.
- Rechazada: La operación falló.
Creando una Promesa
Puedes crear una nueva Promesa usando el constructor Promise, que toma una función (comúnmente llamada "executor") con dos parámetros: resolve
y reject
.
const miPromesa = new Promise((resolve, reject) => {
// Operación asíncrona
setTimeout(() => {
const exito = true;
if (exito) {
resolve('¡Operación completada!');
} else {
reject(new Error('¡Operación fallida!'));
}
}, 1000);
});
Consumiendo Promesas
Una vez que tienes una Promesa, puedes adjuntar manejadores usando los métodos then()
, catch()
, y finally()
.
miPromesa
.then(resultado => {
console.log('Éxito:', resultado);
})
.catch(error => {
console.error('Error:', error);
})
.finally(() => {
console.log('Promesa resuelta (cumplida o rechazada)');
});
Encadenando Promesas
Una de las características más poderosas de las Promesas es la capacidad de encadenarlas. Cada then()
devuelve una nueva Promesa, permitiéndote crear una secuencia de operaciones asíncronas.
obtenerDatosUsuario(idUsuario)
.then(datosUsuario => {
return obtenerPostsUsuario(datosUsuario.nombreUsuario);
})
.then(posts => {
return obtenerComentariosPost(posts[0].id);
})
.then(comentarios => {
console.log('Comentarios:', comentarios);
})
.catch(error => {
console.error('Error en la cadena:', error);
});
Promise.all y Promise.race
La API de Promise incluye varios métodos estáticos para trabajar con múltiples promesas.
Promise.all
Promise.all
toma un array de promesas y devuelve una nueva promesa que se cumple cuando todas las promesas en el array se cumplen, o se rechaza tan pronto como una de ellas se rechaza.
const promesa1 = obtenerDatosUsuario(1);
const promesa2 = obtenerDatosUsuario(2);
const promesa3 = obtenerDatosUsuario(3);
Promise.all([promesa1, promesa2, promesa3])
.then(resultados => {
console.log('Todos resueltos:', resultados);
})
.catch(error => {
console.error('Al menos una promesa rechazada:', error);
});
Promise.race
Promise.race
devuelve una promesa que se cumple o rechaza tan pronto como una de las promesas en el array se cumple o rechaza.
const promesa1 = new Promise(resolve => setTimeout(() => resolve('Primero'), 500));
const promesa2 = new Promise(resolve => setTimeout(() => resolve('Segundo'), 100));
Promise.race([promesa1, promesa2])
.then(resultado => {
console.log('Resultado más rápido:', resultado); // Mostrará "Segundo"
});
Async/Await: Trabajando con Promesas
ES2017 introdujo async
y await
, que hacen que trabajar con Promesas sea aún más intuitivo.
async function obtenerDatosUsuarioYPosts(idUsuario) {
try {
const datosUsuario = await obtenerDatosUsuario(idUsuario);
const posts = await obtenerPostsUsuario(datosUsuario.nombreUsuario);
const comentarios = await obtenerComentariosPost(posts[0].id);
console.log('Comentarios:', comentarios);
return comentarios;
} catch (error) {
console.error('Error:', error);
throw error;
}
}
Patrones Comunes de Promesas
Ejecutando Tareas Asíncronas Secuencialmente
async function procesarSecuencialmente(elementos) {
const resultados = [];
for (const elemento of elementos) {
const resultado = await procesarElemento(elemento);
resultados.push(resultado);
}
return resultados;
}
Ejecución Paralela con Límite
async function procesarConLimiteConcurrencia(elementos, limiteConcurrencia = 3) {
const resultados = [];
const enEjecucion = [];
for (const elemento of elementos) {
const promesa = procesarElemento(elemento).then(resultado => {
resultados.push(resultado);
enEjecucion.splice(enEjecucion.indexOf(promesa), 1);
});
enEjecucion.push(promesa);
if (enEjecucion.length >= limiteConcurrencia) {
await Promise.race(enEjecucion);
}
}
// Esperar todas las promesas restantes
await Promise.all(enEjecucion);
return resultados;
}
Manejo de Errores en Promesas
El manejo adecuado de errores es crucial cuando se trabaja con Promesas. Aquí hay algunas mejores prácticas:
- Siempre incluye un
catch
al final de tus cadenas de Promesas. - Dentro de funciones async, usa bloques try/catch alrededor de expresiones await.
- Considera lanzar errores personalizados para facilitar la depuración.
Conclusión
Las Promesas han revolucionado la programación asíncrona en JavaScript, haciéndola más manejable e intuitiva. Al entender cómo crear, consumir y encadenar Promesas, estarás mejor equipado para manejar la naturaleza asíncrona de JavaScript, especialmente en aplicaciones web modernas donde las operaciones asíncronas son la norma.
A medida que continúes trabajando con Promesas, recuerda que async/await está construido sobre Promesas, por lo que una sólida comprensión de las Promesas es esencial incluso si principalmente usas la sintaxis más nueva.
Sobre el Autor
David Lee
David es un desarrollador senior de JavaScript y educador con más de 10 años de experiencia. Se especializa en desarrollo front-end y le encanta compartir su conocimiento a través de artículos, tutoriales y cursos en línea.