Node
Tutorial Node: Promises
Aprende a manejar Promises en Node.js con then(), catch() y cómo crear Promises personalizadas para operaciones asíncronas robustas.
Aprende Node y certifícatethen() y catch()
Los métodos then() y catch() constituyen la interfaz fundamental para trabajar con Promises en Node.js. Estos métodos permiten definir qué debe ocurrir cuando una operación asíncrona se completa exitosamente o cuando falla, proporcionando un mecanismo elegante para manejar el flujo de ejecución asíncrono.
Método then()
El método then() se ejecuta cuando una Promise se resuelve correctamente. Este método acepta hasta dos parámetros: una función para manejar el caso de éxito y opcionalmente otra para manejar el caso de error.
const fs = require('fs').promises;
// Leer un archivo usando Promises
fs.readFile('datos.txt', 'utf8')
.then(contenido => {
console.log('Archivo leído exitosamente:');
console.log(contenido);
});
El valor de retorno de then() es siempre una nueva Promise, lo que permite encadenar múltiples operaciones asíncronas de forma secuencial:
const fs = require('fs').promises;
fs.readFile('entrada.txt', 'utf8')
.then(contenido => {
// Procesar el contenido
const contenidoProcesado = contenido.toUpperCase();
return contenidoProcesado;
})
.then(contenidoFinal => {
// Escribir el resultado procesado
return fs.writeFile('salida.txt', contenidoFinal);
})
.then(() => {
console.log('Archivo procesado y guardado correctamente');
});
Método catch()
El método catch() maneja los errores que pueden ocurrir durante la ejecución de una Promise. Se ejecuta cuando la Promise es rechazada o cuando se produce una excepción en cualquier punto de la cadena:
const fs = require('fs').promises;
fs.readFile('archivo_inexistente.txt', 'utf8')
.then(contenido => {
console.log(contenido);
})
.catch(error => {
console.error('Error al leer el archivo:', error.message);
});
Es importante entender que catch() captura errores de toda la cadena anterior de then(). Si cualquier operación en la cadena falla, la ejecución salta directamente al catch() más cercano:
const fs = require('fs').promises;
fs.readFile('datos.txt', 'utf8')
.then(contenido => {
// Si este procesamiento falla, se ejecutará el catch()
const datos = JSON.parse(contenido);
return datos;
})
.then(datos => {
// Esta operación también puede fallar
return fs.writeFile('resultado.json', JSON.stringify(datos, null, 2));
})
.catch(error => {
// Maneja errores de cualquier punto de la cadena
console.error('Error en el procesamiento:', error.message);
});
Encadenamiento y propagación de errores
Una característica fundamental del encadenamiento de Promises es que los errores se propagan automáticamente hacia abajo en la cadena hasta encontrar un catch(). Esto permite manejar múltiples operaciones con un solo manejador de errores:
const https = require('https');
function realizarPeticion(url) {
return new Promise((resolve, reject) => {
https.get(url, (res) => {
let datos = '';
res.on('data', chunk => {
datos += chunk;
});
res.on('end', () => {
if (res.statusCode === 200) {
resolve(datos);
} else {
reject(new Error(`HTTP ${res.statusCode}`));
}
});
}).on('error', reject);
});
}
realizarPeticion('https://api.ejemplo.com/datos')
.then(respuesta => {
return JSON.parse(respuesta);
})
.then(datos => {
console.log('Datos recibidos:', datos.length, 'elementos');
return datos.filter(item => item.activo);
})
.then(datosActivos => {
console.log('Elementos activos:', datosActivos.length);
})
.catch(error => {
// Maneja errores de la petición HTTP, parsing JSON o filtrado
console.error('Error en la operación:', error.message);
});
Manejo granular de errores
Aunque catch() al final de la cadena es útil para manejo general, también puedes insertar manejadores de error específicos en puntos concretos de la cadena:
const fs = require('fs').promises;
fs.readFile('configuracion.json', 'utf8')
.catch(error => {
// Manejo específico para archivo de configuración faltante
console.log('Usando configuración por defecto');
return '{"puerto": 3000, "debug": false}';
})
.then(contenido => {
const config = JSON.parse(contenido);
console.log('Configuración cargada:', config);
return config;
})
.then(config => {
// Usar la configuración para inicializar la aplicación
console.log(`Servidor iniciando en puerto ${config.puerto}`);
})
.catch(error => {
// Manejo de errores de parsing o inicialización
console.error('Error fatal:', error.message);
});
Transformación de errores
Los métodos then() y catch() también permiten transformar errores para proporcionar información más útil o recuperarse de fallos específicos:
const fs = require('fs').promises;
function leerArchivoConRespaldo(archivo, archivoRespaldo) {
return fs.readFile(archivo, 'utf8')
.catch(error => {
if (error.code === 'ENOENT') {
console.log(`Archivo ${archivo} no encontrado, usando respaldo`);
return fs.readFile(archivoRespaldo, 'utf8');
}
// Re-lanzar otros tipos de error
throw error;
});
}
leerArchivoConRespaldo('datos.txt', 'datos_respaldo.txt')
.then(contenido => {
console.log('Contenido leído:', contenido.substring(0, 100));
})
.catch(error => {
console.error('No se pudo leer ningún archivo:', error.message);
});
Esta aproximación con then() y catch() proporciona un control preciso sobre el flujo asíncrono, permitiendo crear aplicaciones robustas que manejan tanto los casos de éxito como los escenarios de error de manera elegante y predecible.
Creación de Promises
Aunque Node.js proporciona muchas APIs que ya devuelven Promises, en ocasiones necesitarás crear tus propias Promises para encapsular operaciones asíncronas personalizadas o para convertir APIs basadas en callbacks al patrón de Promises.
Constructor de Promise
Una Promise se crea utilizando el constructor Promise, que acepta una función ejecutora como parámetro. Esta función recibe dos argumentos: resolve
y reject
, que son funciones para indicar el resultado de la operación asíncrona:
const miPromise = new Promise((resolve, reject) => {
// Lógica asíncrona aquí
const exito = true;
if (exito) {
resolve('Operación completada exitosamente');
} else {
reject(new Error('La operación falló'));
}
});
La función ejecutora se ejecuta inmediatamente cuando se crea la Promise. Es importante entender que solo debes llamar a resolve()
o reject()
una vez, ya que una Promise solo puede cambiar de estado una vez.
Promisificación de callbacks
Una aplicación común de la creación de Promises es convertir funciones basadas en callbacks al patrón de Promises. Node.js incluye muchas APIs que siguen el patrón de callback con error como primer parámetro:
const fs = require('fs');
function leerArchivo(ruta) {
return new Promise((resolve, reject) => {
fs.readFile(ruta, 'utf8', (error, datos) => {
if (error) {
reject(error);
} else {
resolve(datos);
}
});
});
}
// Uso de la función promisificada
leerArchivo('ejemplo.txt')
.then(contenido => {
console.log('Contenido del archivo:', contenido);
})
.catch(error => {
console.error('Error al leer:', error.message);
});
Operaciones asíncronas personalizadas
Las Promises son especialmente útiles para encapsular operaciones asíncronas complejas que involucran múltiples pasos o condiciones:
function procesarDatos(datos, tiempoEspera) {
return new Promise((resolve, reject) => {
// Validar entrada
if (!datos || datos.length === 0) {
reject(new Error('Los datos no pueden estar vacíos'));
return;
}
// Simular procesamiento asíncrono
setTimeout(() => {
try {
const resultado = datos.map(item => ({
...item,
procesado: true,
timestamp: Date.now()
}));
resolve(resultado);
} catch (error) {
reject(new Error(`Error en procesamiento: ${error.message}`));
}
}, tiempoEspera);
});
}
// Uso de la Promise personalizada
const datosOriginales = [
{ id: 1, nombre: 'Usuario 1' },
{ id: 2, nombre: 'Usuario 2' }
];
procesarDatos(datosOriginales, 1000)
.then(datosProcesados => {
console.log('Datos procesados:', datosProcesados);
})
.catch(error => {
console.error('Error:', error.message);
});
Promises con operaciones de red
Un caso práctico común es crear Promises para operaciones de red que no están disponibles de forma nativa como Promises:
const http = require('http');
function realizarPeticionHTTP(url, opciones = {}) {
return new Promise((resolve, reject) => {
const peticion = http.request(url, opciones, (respuesta) => {
let datos = '';
// Acumular datos de la respuesta
respuesta.on('data', chunk => {
datos += chunk;
});
// Procesar respuesta completa
respuesta.on('end', () => {
if (respuesta.statusCode >= 200 && respuesta.statusCode < 300) {
resolve({
statusCode: respuesta.statusCode,
headers: respuesta.headers,
body: datos
});
} else {
reject(new Error(`HTTP ${respuesta.statusCode}: ${datos}`));
}
});
});
// Manejar errores de conexión
peticion.on('error', (error) => {
reject(new Error(`Error de conexión: ${error.message}`));
});
// Configurar timeout
peticion.setTimeout(5000, () => {
peticion.destroy();
reject(new Error('Timeout: La petición tardó demasiado'));
});
peticion.end();
});
}
// Uso de la Promise de red
realizarPeticionHTTP('http://httpbin.org/json')
.then(respuesta => {
console.log('Status:', respuesta.statusCode);
console.log('Datos:', JSON.parse(respuesta.body));
})
.catch(error => {
console.error('Error en petición:', error.message);
});
Promises con validación y transformación
Las Promises personalizadas pueden incluir lógica de validación y transformación compleja antes de resolver o rechazar:
function validarYProcesarUsuario(datosUsuario) {
return new Promise((resolve, reject) => {
// Validaciones síncronas
if (!datosUsuario.email || !datosUsuario.email.includes('@')) {
reject(new Error('Email inválido'));
return;
}
if (!datosUsuario.nombre || datosUsuario.nombre.length < 2) {
reject(new Error('Nombre debe tener al menos 2 caracteres'));
return;
}
// Simulación de validación asíncrona (ej: verificar email único)
setTimeout(() => {
const emailExiste = Math.random() < 0.3; // 30% probabilidad
if (emailExiste) {
reject(new Error('El email ya está registrado'));
} else {
// Procesar y normalizar datos
const usuarioProcesado = {
id: Date.now(),
nombre: datosUsuario.nombre.trim(),
email: datosUsuario.email.toLowerCase(),
fechaRegistro: new Date().toISOString(),
activo: true
};
resolve(usuarioProcesado);
}
}, 500);
});
}
// Uso con manejo de diferentes tipos de error
const nuevoUsuario = {
nombre: 'Juan Pérez',
email: 'juan@ejemplo.com'
};
validarYProcesarUsuario(nuevoUsuario)
.then(usuario => {
console.log('Usuario creado:', usuario);
})
.catch(error => {
console.error('Error de validación:', error.message);
});
Buenas prácticas en la creación
Al crear Promises personalizadas, es importante seguir ciertas mejores prácticas:
- Maneja siempre los errores: Asegúrate de que cualquier error posible resulte en una llamada a
reject()
.
function operacionSegura() {
return new Promise((resolve, reject) => {
try {
// Operación que puede fallar
const resultado = JSON.parse(datosJSON);
resolve(resultado);
} catch (error) {
reject(error);
}
});
}
- No mezcles callbacks y Promises: Si una función devuelve una Promise, no uses también callbacks.
// Incorrecto: mezclar patrones
function malaFuncion(callback) {
return new Promise((resolve, reject) => {
// Confuso: ¿callback o Promise?
});
}
// Correcto: solo Promise
function buenaFuncion() {
return new Promise((resolve, reject) => {
// Solo lógica de Promise
});
}
- Resuelve con valores útiles: Proporciona datos estructurados y útiles cuando resuelvas la Promise.
function obtenerEstadisticas() {
return new Promise((resolve, reject) => {
// Procesar datos...
resolve({
total: 150,
activos: 120,
inactivos: 30,
ultimaActualizacion: new Date()
});
});
}
La creación de Promises te permite adaptar cualquier operación asíncrona al patrón de Promises, proporcionando una interfaz consistente y predecible para el manejo de operaciones asíncronas en tus aplicaciones Node.js.
Otras lecciones de Node
Accede a todas las lecciones de Node y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Instalación De Node.js
Introducción Y Entorno
Fundamentos Del Entorno Node.js
Introducción Y Entorno
Estructura De Proyecto Y Package.json
Introducción Y Entorno
Introducción A Node
Introducción Y Entorno
Gestor De Versiones Nvm
Introducción Y Entorno
Repl De Nodejs
Introducción Y Entorno
Ejercicios de programación de Node
Evalúa tus conocimientos de esta lección Promises con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.