preloader

Errores personalizados y cómo extender Error en JavaScript

Errores personalizados y cómo extender Error

Cuando desarrollamos aplicaciones, a menudo necesitamos definir nuestras propias clases de error para reflejar problemas específicos que pueden surgir en nuestras tareas. Por ejemplo, para problemas en las operaciones de red podríamos necesitar un HttpError, para operaciones de base de datos un DbError, y para búsquedas fallidas un NotFoundError, entre otros.

Nuestros errores personalizados deberían incluir propiedades básicas como message, name, y preferentemente stack. Además, pueden tener propiedades adicionales propias, como statusCode en HttpError con valores como 404, 403 o 500.

Extender Error

Aunque en JavaScript se puede usar throw con cualquier objeto, es preferible que nuestras clases de error hereden de Error. De esta manera, podemos utilizar obj instanceof Error para identificar objetos de error, lo cual es una práctica recomendada.

A medida que nuestras aplicaciones crecen, nuestros errores personalizados tienden a formar una jerarquía natural. Por ejemplo, HttpTimeoutError puede heredar de HttpError.

Veamos un ejemplo de cómo extender Error creando una clase ValidationError:

				
					class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

function test() {
  throw new ValidationError("¡Algo salió mal!");
}

try {
  test();
} catch (err) {
  console.log(err.message); // ¡Algo salió mal!
  console.log(err.name);    // ValidationError
  console.log(err.stack);   // Rastreo de pila
}

				
			

En el constructor de ValidationError, llamamos a super(message) para inicializar la propiedad message y luego asignamos this.name al valor correcto.

Uso en una función de ejemplo

Consideremos una función readUser(json) que debería leer y validar un objeto JSON con los datos del usuario. Aquí hay un ejemplo de un JSON válido:

				
					let json = `{ "name": "John", "age": 30 }`;

				
			

Internamente, usaremos JSON.parse para parsear el JSON. Si el JSON está mal formado, JSON.parse lanzará un SyntaxError. Pero incluso si el JSON es sintácticamente correcto, los datos pueden no ser válidos. Por ejemplo, pueden faltar campos obligatorios como name y age.

Nuestra función readUser(json) no solo parseará el JSON, sino que también validará los datos. Si faltan campos obligatorios, lanzará un ValidationError.

Aquí está el código de ValidationError y readUser:

				
					class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

function readUser(json) {
  let user = JSON.parse(json);

  if (!user.age) {
    throw new ValidationError("Falta el campo: age");
  }
  if (!user.name) {
    throw new ValidationError("Falta el campo: name");
  }

  return user;
}

try {
  let user = readUser('{ "age": 25 }');
} catch (err) {
  if (err instanceof ValidationError) {
    console.log("Datos inválidos: " + err.message); // Datos inválidos: Falta el campo: name
  } else if (err instanceof SyntaxError) {
    console.log("Error de sintaxis en JSON: " + err.message);
  } else {
    throw err; // Relanzar error desconocido
  }
}

				
			

Herencia adicional

La clase ValidationError es bastante genérica. Vamos a crear una clase más específica PropertyRequiredError para los casos en los que faltan propiedades específicas:

				
					class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

class PropertyRequiredError extends ValidationError {
  constructor(property) {
    super("Falta la propiedad: " + property);
    this.name = "PropertyRequiredError";
    this.property = property;
  }
}

function readUser(json) {
  let user = JSON.parse(json);

  if (!user.age) {
    throw new PropertyRequiredError("age");
  }
  if (!user.name) {
    throw new PropertyRequiredError("name");
  }

  return user;
}

try {
  let user = readUser('{ "age": 25 }');
} catch (err) {
  if (err instanceof ValidationError) {
    console.log("Datos inválidos: " + err.message); // Datos inválidos: Falta la propiedad: name
    console.log(err.name);   // PropertyRequiredError
    console.log(err.property); // name
  } else if (err instanceof SyntaxError) {
    console.log("Error de sintaxis en JSON: " + err.message);
  } else {
    throw err; // Relanzar error desconocido
  }
}

				
			

Empacado de excepciones

El propósito de readUser es leer y validar datos del usuario. A medida que la función crece, puede generar múltiples tipos de errores. Para simplificar el manejo de errores, podemos empaquetar excepciones en una clase ReadError:

				
					class ReadError extends Error {
  constructor(message, cause) {
    super(message);
    this.cause = cause;
    this.name = 'ReadError';
  }
}

class ValidationError extends Error { /*...*/ }
class PropertyRequiredError extends ValidationError { /* ... */ }

function validateUser(user) {
  if (!user.age) {
    throw new PropertyRequiredError("age");
  }

  if (!user.name) {
    throw new PropertyRequiredError("name");
  }
}

function readUser(json) {
  let user;

  try {
    user = JSON.parse(json);
  } catch (err) {
    if (err instanceof SyntaxError) {
      throw new ReadError("Error de sintaxis", err);
    } else {
      throw err;
    }
  }

  try {
    validateUser(user);
  } catch (err) {
    if (err instanceof ValidationError) {
      throw new ReadError("Error de validación", err);
    } else {
      throw err;
    }
  }
}

try {
  readUser('{json malformado}');
} catch (e) {
  if (e instanceof ReadError) {
    console.log(e);
    console.log("Error original: " + e.cause);
  } else {
    throw e;
  }
}

				
			

En este código, readUser detecta errores de sintaxis y validación, y lanza errores ReadError en su lugar. El código que llama a readUser solo necesita manejar ReadError, lo cual simplifica significativamente el manejo de errores.

Resumen

  • Podemos extender Error y otras clases de error nativas.
  • Es crucial usar instanceof para verificar tipos de error específicos.
  • La técnica de empacado de excepciones permite manejar errores de bajo nivel empaquetándolos en errores de nivel superior.

Este enfoque ayuda a mantener el código limpio y manejable a medida que crecen nuestras aplicaciones.

Related Post

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *