El manejo robusto de errores distingue el JavaScript listo para produccion de las demos fragiles. Esta guia cubre los patrones try/catch, el manejo de errores async y las clases de error personalizadas.
Fundamentos de try/catch
El bloque try/catch/finally es la base del manejo sincrono de errores en JavaScript.
// try/catch/finally fundamentals
// Basic structure
try {
const data = JSON.parse(userInput); // Can throw SyntaxError
processData(data);
} catch (error) {
if (error instanceof SyntaxError) {
console.error('Invalid JSON:', error.message);
} else {
throw error; // Re-throw errors you can't handle here
}
} finally {
cleanup(); // Always runs — use for resource cleanup
}
// What catch does NOT catch:
// 1. Errors in async callbacks (use async/await instead)
// 2. Errors in setTimeout/setInterval
// 3. Errors in event handlers
// Wrong: setTimeout error is not caught
try {
setTimeout(() => {
throw new Error('This is NOT caught by outer try/catch');
}, 100);
} catch (e) {
// This never runs
}
// Right: wrap async code
setTimeout(() => {
try {
throw new Error('This IS caught');
} catch (e) {
console.error(e);
}
}, 100);
// Distinguishing error types
function handleError(error) {
if (error instanceof TypeError) {
// Null dereference, wrong type
} else if (error instanceof RangeError) {
// Array out of bounds, recursion limit
} else if (error instanceof NetworkError) {
// Custom: network failure
} else {
// Unknown: re-throw
throw error;
}
}Manejo de errores async/await
Las funciones async devuelven Promises, por lo que los errores deben capturarse de manera diferente.
// Async/Await Error Handling
// 1. Basic async error handling
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request was aborted');
return null;
}
throw error; // Re-throw for caller to handle
}
}
// 2. Handling multiple async operations
async function loadDashboard(userId) {
// Run in parallel, catch errors individually
const [user, posts, stats] = await Promise.allSettled([
fetchUser(userId),
fetchPosts(userId),
fetchStats(userId),
]);
return {
user: user.status === 'fulfilled' ? user.value : null,
posts: posts.status === 'fulfilled' ? posts.value : [],
stats: stats.status === 'fulfilled' ? stats.value : {},
errors: [user, posts, stats]
.filter(r => r.status === 'rejected')
.map(r => r.reason),
};
}
// 3. Async error handling utility
async function tryCatchAsync<T>(
promise: Promise<T>
): Promise<[T | null, Error | null]> {
try {
const data = await promise;
return [data, null];
} catch (error) {
return [null, error instanceof Error ? error : new Error(String(error))];
}
}
// Usage: avoids nested try/catch
const [user, error] = await tryCatchAsync(fetchUser(id));
if (error) {
console.error('Failed to fetch user:', error.message);
return;
}
console.log(user.name);Clases de error personalizadas
Las clases de error personalizadas permiten el manejo estructurado de errores.
// Custom Error Classes
class AppError extends Error {
public readonly code: string;
public readonly statusCode: number;
public readonly isOperational: boolean;
constructor(
message: string,
code: string,
statusCode = 500,
isOperational = true
) {
super(message);
this.name = this.constructor.name;
this.code = code;
this.statusCode = statusCode;
this.isOperational = isOperational;
// Maintains proper prototype chain (important for instanceof)
Object.setPrototypeOf(this, new.target.prototype);
// Captures stack trace (V8 only)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
class ValidationError extends AppError {
public readonly fields: Record<string, string[]>;
constructor(message: string, fields: Record<string, string[]> = {}) {
super(message, 'VALIDATION_ERROR', 400);
this.fields = fields;
}
}
class NotFoundError extends AppError {
constructor(resource: string, id: string | number) {
super(`${resource} with id ${id} not found`, 'NOT_FOUND', 404);
}
}
class NetworkError extends AppError {
public readonly url: string;
constructor(message: string, url: string) {
super(message, 'NETWORK_ERROR', 503);
this.url = url;
}
}
// Usage
function findUser(id: number) {
const user = db.find(id);
if (!user) throw new NotFoundError('User', id);
return user;
}
// Error type checking
try {
findUser(999);
} catch (error) {
if (error instanceof NotFoundError) {
res.status(404).json({ error: error.message, code: error.code });
} else if (error instanceof ValidationError) {
res.status(400).json({ error: error.message, fields: error.fields });
} else {
// Unknown error — log and return 500
logger.error('Unexpected error', { error });
res.status(500).json({ error: 'Internal server error' });
}
}Manejadores de errores globales
Algunos errores pasan a traves de los bloques try/catch locales. Los manejadores globales son tu ultima defensa.
// Global Error Handlers
// Browser: uncaught synchronous errors
window.onerror = (message, source, lineno, colno, error) => {
console.error('Uncaught error:', { message, source, lineno, colno, error });
reportToSentry(error);
return true; // Prevents default browser error dialog
};
// Browser: unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
reportToSentry(event.reason);
event.preventDefault(); // Prevents console error
});
// Node.js: uncaught exceptions
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
reportToSentry(error);
// Exit after logging — cannot safely continue after uncaughtException
process.exit(1);
});
// Node.js: unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection at:', promise, 'reason:', reason);
reportToSentry(reason instanceof Error ? reason : new Error(String(reason)));
});
// Express.js: error middleware (must have 4 params)
app.use((err, req, res, next) => {
const statusCode = err.statusCode || 500;
const isOperational = err.isOperational || false;
if (!isOperational) {
// Unexpected error — log full details
console.error('Unexpected error:', err);
reportToSentry(err, { url: req.url, method: req.method });
}
res.status(statusCode).json({
error: {
message: isOperational ? err.message : 'Internal server error',
code: err.code || 'INTERNAL_ERROR',
},
});
});Preguntas frecuentes
Debo capturar cada error?
No. Solo captura errores que puedas manejar significativamente en el nivel actual.
Cual es la diferencia entre throw y throw new Error()?
Lanzar objetos no-Error pierde el stack trace. Siempre usa new Error().
Como manejar errores en Promise.all()?
Promise.all() rechaza tan pronto como una promesa es rechazada. Usa Promise.allSettled() para todos los resultados.
Que incluir en los mensajes de error?
Los mensajes deben ser especificos. Nunca incluyas datos sensibles.