Gestión de Errores#
En JavaScript, se pueden capturar y manejar errores para asegurar que el código continúe funcionando adecuadamente, incluso cuando algo falla.
Cuando algo falla en JavaScript, el hilo de ejecución se detiene y deja de funcionar. Para evitar que esto ocurra, es posible capturar y manejar errores conocidos. Los errores no capturados paran el hilo en el que están. Puede ser el hilo principal o la ejecución de callbacks. Depende dónde se produzca el error puede ser maś o menos catastrófico y dejar la web inutilizable.
Los errores pueden ser por errores, malas prácticas o simplemente porque falla algo sobre lo que no tenemos control como la red.
Objeto Error#
Cuando algo falla, se lanza un objeto Error. Este objeto contiene propiedades específicas como message
, name
, fileName
, lineNumber
, columnNumber
, y stack
. Además, tiene un método útil llamado toString()
.
Aunque Error
es genérico, también existen errores más específicos como EvalError
, InternalError
, SyntaxError
, RangeError
, TypeError
y URIError
.
Ese error se puede lanzar con throw
y capturar con un bloque try...catch
. Si es tratado por el catch, el error no pasa de ahí y no corta la ejecución del hilo. Además, se puede aprovechar el catch
para solucionar el problema que lo ha causado.
try {
throw new Error('¡Ups!')
} catch (e) {
console.error(e.name + ': ' + e.message)
}
Error: ¡Ups!
Manejar Errores#
Los errores pueden ser manejados de forma genérica o específica usando instanceof
. Además, es posible crear errores personalizados. Si es necesario ejecutar código independientemente de si ocurre un error, se utiliza finally()
.
try {
foo.bar();
} catch (e) {
console.error(e.toString());
if (e instanceof EvalError) {
console.error(e.name + ': ' + e.message);
} else if (e instanceof RangeError) {
console.error(e.name + ': ' + e.message);
} // ... etc
} finally {
closeMyFile();
}
ReferenceError: foo is not defined
Stack trace:
ReferenceError: closeMyFile is not defined
at <anonymous>:11:3
En ocasiones queremos tratar el error en una función pero que siga escalando para que lo trate una función superior. Esto es hacer un rethrow
y consiste en poner un throw
dentro del catch
:
async function fetchUser(id) {
const res = await fetch(`/users/${id}`);
if (!res.ok) throw new Error("User not found");
return res.json();
}
async function getUser(id) {
try {
const user = await fetchUser(id);
return user;
} catch (err) {
console.error("Fetching user failed:", err.message);
throw new Error("Unable to load user profile");
}
}
Es posible lanzar errores personalizados utilizando el constructor de Error.
class ValidationError extends Error {
constructor(field, message) {
super(`${field}: ${message}`);
this.name = "ValidationError";
this.field = field;
}
}
function validateUser(user) {
if (!user.email.includes("@")) {
throw new ValidationError("email", "Invalid email format");
}
if (!user.age || user.age < 18) {
throw new ValidationError("age", "User must be 18+");
}
}
Errores en Promesas#
Cuando una promesa no puede ser cumplida, se ejecuta la función reject()
, la cual puede ser capturada en el bloque .catch()
. Sin embargo, los errores en promesas no pueden ser capturados directamente con try...catch
debido a que .catch()
siempre retorna otra promesa.
Por tanto el .catch()
de las promesas no es igual y puede recibir tanto un Error
como cualquier otra cosa que se pase a reject
. De todas formas, un throw
dentro de un .then()
o .catch()
provoca que retorne una promesa fallida que puede ser capturada por .catch()
. Como se ve en el ejemplo:
let promesa3 = new Promise(function promesa(resolve, reject) {
try {
if (Math.random() > 0.5) resolve('Funciona 3');
else throw new Error('No funciona 3');
} catch (error) {
reject(error.message);
}
});
promesa3.then(function r(message) {
console.log(message);
}).catch(function c(error) {
throw new Error(error);
}).catch(function c(error) {
console.error(error.toString());
});
Error: No funciona 3
Promise { undefined }
En el interior de cualquier función ejecutora de promesas o de callback de .then()
o .catch()
se puede usar el bloque tradicional try...catch
siempre que se use reject
o throw
en cada caso.
En ocasiones se puede decidir trabajar sólo con la sintaxis de las promesas o con el método tradicional.
Errores en Async/Await#
Cuando se utiliza async/await
, se pueden manejar errores dentro de un bloque try...catch
. Si una promesa es rechazada (reject
), await
lanza un error que puede ser capturado con catch
.
async function getData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Error de servidor: ${response.status} ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error:', error.message);
throw error;
}
}
async function fetchExample() {
const url = 'https://api.foo.com/data';
try {
const data = await getData(url);
console.log('Datos recibidos:', data);
} catch (error) {
console.error('Error en la solicitud:', error);
}
}
fetchExample();
Promise { <pending> }
En código moderno, se recomienda usar async/await
con try...catch
por su legibilidad. Frente a la sintaxis de las promesas.
Errores en Observables#
Los Observables pueden manejar errores ejecutando la función error()
del Observer. Además, pueden retornar un error usando throwError()
y este error puede ser capturado con catchError()
dentro de las pipes.
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
const observable = new Observable(subscriber => {
try {
subscriber.next('Next value');
throw new Error('Something went wrong');
} catch (error) {
subscriber.error(error);
}
});
observable.pipe(
catchError(error => {
console.error('Caught error:', error);
return throwError(error); // Propaga el error
})
).subscribe({
next(value) { console.log(value); },
error(err) { console.error('Error handler:', err); }
});
En caso de que un Observable falle, es posible reintentar una cantidad limitada de veces utilizando retry()
.
import { retry } from 'rxjs/operators';
observable.pipe(
retry(3),
catchError(error => {
console.error('Caught error after retries:', error);
return throwError(error);
})
).subscribe({
next(value) { console.log(value); },
error(err) { console.error('Error handler:', err); }
});