Node
Tutorial Node: Manejo de errores en código asíncrono
Aprende a gestionar errores en código asíncrono de Node.js usando async/await y .catch() para aplicaciones robustas y mantenibles.
Aprende Node y certifícatetry/catch con async/await
El manejo de errores con async/await representa la forma más moderna y legible de gestionar excepciones en código asíncrono de Node.js. Esta sintaxis permite escribir código asíncrono que se lee de manera similar al código síncrono, facilitando tanto la escritura como el mantenimiento.
Sintaxis básica del manejo de errores
La estructura fundamental para capturar errores con async/await utiliza los bloques try/catch
tradicionales de JavaScript. Cuando una función asíncrona lanza una excepción o una Promise es rechazada, el error se propaga automáticamente al bloque catch
más cercano.
async function leerArchivo() {
try {
const fs = require('fs').promises;
const contenido = await fs.readFile('archivo.txt', 'utf8');
console.log('Contenido del archivo:', contenido);
return contenido;
} catch (error) {
console.error('Error al leer el archivo:', error.message);
throw error; // Re-lanzar si es necesario
}
}
En este ejemplo, cualquier error que ocurra durante la operación de lectura será capturado automáticamente por el bloque catch
. Esto incluye errores como archivos inexistentes, permisos insuficientes o problemas de codificación.
Manejo de múltiples operaciones asíncronas
Cuando necesitas realizar varias operaciones asíncronas en secuencia, cada una puede fallar independientemente. El bloque try/catch
capturará el primer error que ocurra y detendrá la ejecución del resto del código dentro del bloque try
.
const fs = require('fs').promises;
async function procesarArchivos() {
try {
// Leer archivo de configuración
const config = await fs.readFile('config.json', 'utf8');
const configuracion = JSON.parse(config);
// Leer archivo de datos
const datos = await fs.readFile(configuracion.archivoEntrada, 'utf8');
// Procesar y escribir resultado
const resultado = datos.toUpperCase();
await fs.writeFile(configuracion.archivoSalida, resultado);
console.log('Procesamiento completado exitosamente');
} catch (error) {
if (error.code === 'ENOENT') {
console.error('Archivo no encontrado:', error.path);
} else if (error instanceof SyntaxError) {
console.error('Error de formato JSON:', error.message);
} else {
console.error('Error inesperado:', error.message);
}
}
}
Este enfoque permite identificar diferentes tipos de errores y responder de manera apropiada a cada situación. La verificación del código de error y el tipo de excepción ayuda a proporcionar mensajes más específicos y útiles.
Manejo granular con múltiples bloques try/catch
Para situaciones donde necesitas control más fino sobre el manejo de errores, puedes usar múltiples bloques try/catch
anidados o secuenciales. Esto es útil cuando quieres que el fallo de una operación no impida la ejecución de otras operaciones independientes.
const https = require('https');
const fs = require('fs').promises;
async function sincronizarDatos() {
let datosLocales = null;
let datosRemotos = null;
// Intentar cargar datos locales
try {
const contenido = await fs.readFile('datos-locales.json', 'utf8');
datosLocales = JSON.parse(contenido);
console.log('Datos locales cargados correctamente');
} catch (error) {
console.warn('No se pudieron cargar datos locales:', error.message);
datosLocales = {}; // Valor por defecto
}
// Intentar obtener datos remotos
try {
datosRemotos = await obtenerDatosRemotos();
console.log('Datos remotos obtenidos correctamente');
} catch (error) {
console.warn('No se pudieron obtener datos remotos:', error.message);
datosRemotos = datosLocales; // Usar datos locales como respaldo
}
return { local: datosLocales, remoto: datosRemotos };
}
function obtenerDatosRemotos() {
return new Promise((resolve, reject) => {
const req = https.get('https://api.ejemplo.com/datos', (res) => {
let datos = '';
res.on('data', chunk => datos += chunk);
res.on('end', () => {
try {
resolve(JSON.parse(datos));
} catch (error) {
reject(new Error('Respuesta JSON inválida'));
}
});
});
req.on('error', reject);
req.setTimeout(5000, () => {
req.destroy();
reject(new Error('Timeout en la petición'));
});
});
}
Propagación y transformación de errores
Una práctica común es capturar errores para registrarlos o transformarlos antes de propagarlos hacia arriba en la cadena de llamadas. Esto permite mantener un registro detallado de errores mientras se preserva la capacidad de manejo en niveles superiores.
const fs = require('fs').promises;
async function cargarConfiguracion(rutaArchivo) {
try {
const contenido = await fs.readFile(rutaArchivo, 'utf8');
return JSON.parse(contenido);
} catch (error) {
// Transformar el error para proporcionar más contexto
const errorPersonalizado = new Error(
`Error al cargar configuración desde ${rutaArchivo}: ${error.message}`
);
errorPersonalizado.causa = error;
errorPersonalizado.archivo = rutaArchivo;
throw errorPersonalizado;
}
}
async function inicializarAplicacion() {
try {
const config = await cargarConfiguracion('./config/app.json');
console.log('Aplicación inicializada con configuración:', config.nombre);
return config;
} catch (error) {
console.error('Fallo en la inicialización:', error.message);
// Intentar con configuración por defecto
try {
console.log('Intentando cargar configuración por defecto...');
return await cargarConfiguracion('./config/default.json');
} catch (errorDefecto) {
console.error('No se pudo cargar configuración por defecto');
throw new Error('Imposible inicializar la aplicación sin configuración');
}
}
}
Manejo de errores en operaciones paralelas
Cuando ejecutas múltiples operaciones asíncronas en paralelo usando Promise.all()
o Promise.allSettled()
, el manejo de errores requiere consideraciones especiales. Con Promise.all()
, si cualquier Promise falla, toda la operación falla inmediatamente.
const fs = require('fs').promises;
async function procesarArchivosEnParalelo(archivos) {
try {
// Promise.all falla si cualquier archivo falla
const contenidos = await Promise.all(
archivos.map(archivo => fs.readFile(archivo, 'utf8'))
);
console.log(`Se procesaron ${contenidos.length} archivos exitosamente`);
return contenidos;
} catch (error) {
console.error('Error al procesar archivos en paralelo:', error.message);
throw error;
}
}
async function procesarArchivosConTolerancia(archivos) {
try {
// Promise.allSettled no falla, devuelve resultados y errores
const resultados = await Promise.allSettled(
archivos.map(async (archivo) => {
try {
return await fs.readFile(archivo, 'utf8');
} catch (error) {
throw new Error(`Error en ${archivo}: ${error.message}`);
}
})
);
const exitosos = resultados.filter(r => r.status === 'fulfilled');
const fallidos = resultados.filter(r => r.status === 'rejected');
console.log(`Procesados: ${exitosos.length}, Fallidos: ${fallidos.length}`);
// Registrar errores específicos
fallidos.forEach(fallo => {
console.error('Error específico:', fallo.reason.message);
});
return exitosos.map(r => r.value);
} catch (error) {
console.error('Error inesperado en procesamiento tolerante:', error.message);
throw error;
}
}
El uso de async/await con try/catch proporciona un control preciso sobre el flujo de errores en aplicaciones Node.js, permitiendo crear aplicaciones robustas que pueden recuperarse de fallos y proporcionar información útil sobre problemas que puedan surgir durante la ejecución.
.catch() con Promises
El método .catch() representa el enfoque tradicional para el manejo de errores en Promises, proporcionando una forma elegante de capturar y gestionar excepciones en cadenas de operaciones asíncronas. Aunque async/await
ofrece una sintaxis más moderna, entender .catch()
es fundamental para trabajar con código existente y para situaciones donde el encadenamiento de Promises resulta más apropiado.
Sintaxis básica del método .catch()
El método .catch()
se adjunta al final de una cadena de Promises y captura cualquier error que ocurra en cualquier punto de la cadena. Funciona de manera similar a un bloque catch
en el manejo de excepciones síncronas, pero específicamente diseñado para el contexto asíncrono.
const fs = require('fs').promises;
fs.readFile('datos.txt', 'utf8')
.then(contenido => {
console.log('Archivo leído exitosamente');
return contenido.toUpperCase();
})
.then(contenidoMayusculas => {
return fs.writeFile('datos-procesados.txt', contenidoMayusculas);
})
.then(() => {
console.log('Archivo procesado y guardado');
})
.catch(error => {
console.error('Error en la cadena de procesamiento:', error.message);
});
En este ejemplo, el .catch()
capturará errores de cualquiera de las operaciones: lectura del archivo, transformación del contenido o escritura del archivo resultante.
Manejo específico de errores en cadenas complejas
Cuando trabajas con cadenas de Promises más complejas, puedes colocar múltiples .catch()
en diferentes puntos para manejar errores específicos sin interrumpir toda la cadena de operaciones.
const https = require('https');
const fs = require('fs').promises;
function descargarArchivo(url) {
return new Promise((resolve, reject) => {
const req = https.get(url, (res) => {
if (res.statusCode !== 200) {
reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
return;
}
let datos = '';
res.on('data', chunk => datos += chunk);
res.on('end', () => resolve(datos));
});
req.on('error', reject);
req.setTimeout(10000, () => {
req.destroy();
reject(new Error('Timeout en la descarga'));
});
});
}
descargarArchivo('https://api.ejemplo.com/datos.json')
.catch(error => {
console.warn('Error en descarga, usando datos locales:', error.message);
return fs.readFile('datos-respaldo.json', 'utf8');
})
.then(datos => {
return JSON.parse(datos);
})
.then(objetoDatos => {
console.log('Datos procesados:', objetoDatos.titulo);
return objetoDatos;
})
.catch(error => {
console.error('Error al procesar datos JSON:', error.message);
return { titulo: 'Datos por defecto', contenido: [] };
});
Transformación y propagación de errores
El método .catch()
no solo captura errores, sino que también permite transformarlos o crear nuevos errores con información adicional. Si un .catch()
devuelve un valor, la cadena continúa normalmente; si lanza un error, se propaga al siguiente .catch()
.
const fs = require('fs').promises;
function procesarConfiguracion(rutaArchivo) {
return fs.readFile(rutaArchivo, 'utf8')
.then(contenido => {
try {
return JSON.parse(contenido);
} catch (parseError) {
throw new Error(`JSON inválido en ${rutaArchivo}: ${parseError.message}`);
}
})
.catch(error => {
if (error.code === 'ENOENT') {
// Transformar error de archivo no encontrado
const errorPersonalizado = new Error(`Archivo de configuración no encontrado: ${rutaArchivo}`);
errorPersonalizado.tipo = 'ARCHIVO_FALTANTE';
errorPersonalizado.rutaOriginal = rutaArchivo;
throw errorPersonalizado;
}
// Re-lanzar otros errores sin modificar
throw error;
});
}
procesarConfiguracion('./config/app.json')
.then(config => {
console.log('Configuración cargada:', config.nombre);
})
.catch(error => {
if (error.tipo === 'ARCHIVO_FALTANTE') {
console.error('Configuración faltante:', error.message);
console.log('Creando configuración por defecto...');
// Lógica para crear configuración por defecto
} else {
console.error('Error inesperado:', error.message);
}
});
Combinación de .catch() con Promise.all()
Cuando utilizas Promise.all()
para ejecutar múltiples operaciones en paralelo, un solo .catch()
puede capturar errores de cualquiera de las Promises involucradas. Sin embargo, el comportamiento es de "todo o nada": si una falla, todas fallan.
const fs = require('fs').promises;
const archivos = ['archivo1.txt', 'archivo2.txt', 'archivo3.txt'];
Promise.all(archivos.map(archivo =>
fs.readFile(archivo, 'utf8')
.catch(error => {
console.warn(`Error leyendo ${archivo}:`, error.message);
return `Error: ${archivo} no disponible`;
})
))
.then(contenidos => {
console.log('Todos los archivos procesados:');
contenidos.forEach((contenido, index) => {
console.log(`${archivos[index]}: ${contenido.substring(0, 50)}...`);
});
})
.catch(error => {
console.error('Error crítico en el procesamiento paralelo:', error.message);
});
Patrón de recuperación con .catch()
Una técnica común es usar .catch()
para implementar estrategias de recuperación, donde un fallo en una operación desencadena un plan alternativo sin interrumpir el flujo principal de la aplicación.
const https = require('https');
const fs = require('fs').promises;
function obtenerDatosConRespaldo() {
return new Promise((resolve, reject) => {
// Intentar obtener datos remotos
const req = https.get('https://api.principal.com/datos', (res) => {
let datos = '';
res.on('data', chunk => datos += chunk);
res.on('end', () => {
try {
resolve(JSON.parse(datos));
} catch (error) {
reject(new Error('Respuesta JSON inválida del servidor principal'));
}
});
});
req.on('error', reject);
req.setTimeout(5000, () => {
req.destroy();
reject(new Error('Timeout servidor principal'));
});
})
.catch(error => {
console.warn('Servidor principal falló:', error.message);
console.log('Intentando servidor de respaldo...');
// Estrategia de respaldo: servidor alternativo
return new Promise((resolve, reject) => {
const req = https.get('https://backup.api.com/datos', (res) => {
let datos = '';
res.on('data', chunk => datos += chunk);
res.on('end', () => resolve(JSON.parse(datos)));
});
req.on('error', reject);
});
})
.catch(error => {
console.warn('Servidor de respaldo falló:', error.message);
console.log('Usando datos locales en caché...');
// Segunda estrategia: datos locales
return fs.readFile('cache-datos.json', 'utf8')
.then(contenido => JSON.parse(contenido));
})
.catch(error => {
console.error('Todas las fuentes de datos fallaron:', error.message);
// Última estrategia: datos por defecto
return {
mensaje: 'Servicio temporalmente no disponible',
datos: [],
timestamp: new Date().toISOString()
};
});
}
obtenerDatosConRespaldo()
.then(datos => {
console.log('Datos obtenidos exitosamente:', datos);
});
Diferencias clave con async/await
El método .catch()
ofrece mayor flexibilidad para el manejo granular de errores en puntos específicos de una cadena, mientras que async/await
con try/catch
proporciona un control más estructurado pero menos granular. La elección entre ambos enfoques depende del nivel de control que necesites sobre el flujo de errores y la complejidad de las operaciones de recuperación.
// Enfoque con .catch() - control granular
fs.readFile('config.json', 'utf8')
.catch(() => '{}') // Valor por defecto si falla
.then(contenido => JSON.parse(contenido))
.catch(() => ({ configuracionPorDefecto: true })) // Objeto por defecto si JSON falla
.then(config => {
console.log('Configuración final:', config);
});
El patrón .catch()
resulta especialmente útil cuando necesitas implementar múltiples niveles de recuperación o cuando trabajas con APIs que devuelven Promises directamente, proporcionando un control fino sobre cómo y cuándo se manejan los diferentes tipos de errores en tu aplicación 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 Manejo de errores en código asíncrono con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.