Como desarrollador en JavaScript, es probable que hayas escuchado acerca de las promesas y el uso de async/await para manejar su flujo. En este artículo, te explicaré qué son las promesas, cómo se usan y cómo async/await puede ayudar a manejar el flujo de trabajo.
¿Qué son las promesas?
Una promesa en JavaScript es un objeto que representa un valor que puede no estar disponible todavía, pero que se espera que lo esté en algún momento. Las promesas se utilizan para manejar operaciones asíncronas que toman tiempo en completarse, como hacer una solicitud HTTP o leer un archivo.
Estados de una promesa
Las promesas tienen tres estados posibles:
- Pending (Pendiente): Estado inicial, ni cumplida ni rechazada
- Fulfilled (Cumplida): La operación se completó exitosamente
- Rejected (Rechazada): La operación falló
Creando una promesa
Para crear una promesa en JavaScript, utilizamos el constructor Promise. Este constructor toma una función como argumento, que a su vez toma dos argumentos: resolve y reject.
const promise = new Promise((resolve, reject) => {
// Simulamos una operación asíncrona
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve('Operación exitosa');
} else {
reject('Error en la operación');
}
}, 1000);
});
Consumiendo una promesa
Para consumir una promesa, podemos usar los métodos then y catch:
promise
.then((result) => {
console.log('Éxito:', result);
})
.catch((error) => {
console.error('Error:', error);
});
Encadenando promesas
Una de las ventajas de las promesas es que se pueden encadenar:
fetch('/api/users')
.then(response => response.json())
.then(data => {
console.log('Usuarios:', data);
return fetch(`/api/users/${data[0].id}/posts`);
})
.then(response => response.json())
.then(posts => console.log('Posts:', posts))
.catch(error => console.error('Error:', error));
Async/Await: Una sintaxis más limpia
async/await es una forma de manejar promesas en JavaScript que se introdujo en ES2017. Es una sintaxis más limpia y fácil de leer que el encadenamiento de .then().
Función async
Una función async siempre devuelve una promesa:
async function miFuncion() {
return 'Hola mundo';
}
// Es equivalente a:
function miFuncion() {
return Promise.resolve('Hola mundo');
}
Usando await
La palabra clave await solo puede usarse dentro de funciones async:
async function obtenerDatos() {
try {
const response = await fetch('/api/datos');
const data = await response.json();
return data;
} catch (error) {
console.error('Error:', error);
}
}
Convirtiendo el ejemplo anterior con async/await
async function obtenerUsuariosYPosts() {
try {
const usersResponse = await fetch('/api/users');
const users = await usersResponse.json();
const postsResponse = await fetch(`/api/users/${users[0].id}/posts`);
const posts = await postsResponse.json();
console.log('Usuarios:', users);
console.log('Posts:', posts);
} catch (error) {
console.error('Error:', error);
}
}
Comparación: Promesas vs Async/Await
Con promesas (método tradicional)
function procesarDatos() {
return fetch('/api/datos')
.then(response => {
if (!response.ok) {
throw new Error('Error en la respuesta');
}
return response.json();
})
.then(data => {
return procesarInformacion(data);
})
.catch(error => {
console.error('Error:', error);
throw error;
});
}
Con async/await (método moderno)
async function procesarDatos() {
try {
const response = await fetch('/api/datos');
if (!response.ok) {
throw new Error('Error en la respuesta');
}
const data = await response.json();
return procesarInformacion(data);
} catch (error) {
console.error('Error:', error);
throw error;
}
}
Buenas prácticas
1. Manejo de errores
Siempre envuelve tu código await en bloques try/catch:
async function operacionSegura() {
try {
const resultado = await operacionAsincrona();
return resultado;
} catch (error) {
console.error('Error capturado:', error);
// Manejar el error apropiadamente
throw error; // Re-lanzar si es necesario
}
}
2. Paralelización cuando sea posible
// ❌ Secuencial (lento)
async function lento() {
const user1 = await fetch('/api/user/1');
const user2 = await fetch('/api/user/2');
const user3 = await fetch('/api/user/3');
}
// ✅ Paralelo (rápido)
async function rapido() {
const [user1, user2, user3] = await Promise.all([
fetch('/api/user/1'),
fetch('/api/user/2'),
fetch('/api/user/3')
]);
}
3. No usar async/await innecesariamente
// ❌ No necesario
async function suma(a, b) {
return a + b;
}
// ✅ Mejor
function suma(a, b) {
return a + b;
}
Casos de uso comunes
1. Peticiones HTTP
async function obtenerUsuario(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error obteniendo usuario:', error);
throw error;
}
}
2. Operaciones de archivos (Node.js)
const fs = require('fs').promises;
async function leerArchivo(ruta) {
try {
const contenido = await fs.readFile(ruta, 'utf8');
return contenido;
} catch (error) {
console.error('Error leyendo archivo:', error);
throw error;
}
}
3. Timeouts y delays
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function procesoConDelay() {
console.log('Iniciando proceso...');
await delay(2000);
console.log('Proceso completado');
}
Errores comunes que evitar
1. Olvidar await
// ❌ Error: resultado es una promesa, no el valor
async function mal() {
const resultado = fetch('/api/datos');
console.log(resultado); // [object Promise]
}
// ✅ Correcto
async function bien() {
const resultado = await fetch('/api/datos');
console.log(resultado); // Response object
}
2. No manejar errores
// ❌ Error no manejado
async function sinManejoDeErrores() {
const response = await fetch('/api/datos-inexistentes');
const data = await response.json();
}
// ✅ Con manejo de errores
async function conManejoDeErrores() {
try {
const response = await fetch('/api/datos-inexistentes');
const data = await response.json();
} catch (error) {
console.error('Error:', error);
}
}
3. Usar async/await en bucles innecesariamente
// ❌ Secuencial innecesario
async function procesarArray(array) {
const resultados = [];
for (const item of array) {
const resultado = await procesarItem(item);
resultados.push(resultado);
}
return resultados;
}
// ✅ Paralelo cuando sea posible
async function procesarArrayParalelo(array) {
const promesas = array.map(item => procesarItem(item));
return await Promise.all(promesas);
}
Conclusión
Las promesas y async/await son herramientas fundamentales para manejar operaciones asíncronas en JavaScript:
- Promesas: Proporcionan una forma estructurada de manejar operaciones asíncronas
- Async/await: Ofrece una sintaxis más limpia y legible para trabajar con promesas
- Ambas son complementarias: No son mutuamente excluyentes
Cuándo usar cada una
- Usa promesas cuando necesites control granular sobre el flujo asíncrono
- Usa async/await para código más legible y mantenible
- Combina ambas según las necesidades específicas de tu aplicación
Recuerda siempre manejar los errores apropiadamente y considerar la paralelización cuando sea posible para optimizar el rendimiento.
Si quieres profundizar más sobre el tema, te recomiendo revisar la documentación oficial de MDN y practicar con ejemplos reales en tus proyectos.