14 de agosto de 2023

Errores personalizados, extendiendo Error

Cuando desarrollamos algo, a menudo necesitamos nuestras propias clases de error para reflejar cosas específicas que pueden salir mal en nuestras tareas. Para errores en las operaciones de red, podemos necesitar HttpError, para las operaciones de la base de datos DbError, para las operaciones de búsqueda NotFoundError, etc.

Nuestros errores deben admitir propiedades de error básicas como message, name y, preferiblemente, stack. Pero también pueden tener otras propiedades propias, por ejemplo, los objetos HttpError pueden tener una propiedad statusCode con un valor como 404 o 403 o 500.

JavaScript permite usar throw con cualquier argumento, por lo que técnicamente nuestras clases de error personalizadas no necesitan heredarse de Error. Pero si heredamos, entonces es posible usar obj instanceof Error para identificar objetos error. Entonces es mejor heredar de él.

A medida que la aplicación crece, nuestros propios errores forman naturalmente una jerarquía. Por ejemplo, HttpTimeoutError puede heredar de HttpError, y así sucesivamente.

Extendiendo Error

Como ejemplo, consideremos una función readUser(json) que debería leer JSON con los datos del usuario.

Aquí hay un ejemplo de cómo puede verse un json válido:

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

Internamente, usaremos JSON.parse. Si recibe json mal formado, entonces arroja SyntaxError. Pero incluso si json es sintácticamente correcto, eso no significa que sea un usuario válido, ¿verdad? Puede perder los datos necesarios. Por ejemplo, puede no tener propiedades de nombre y edad que son esenciales para nuestros usuarios.

Nuestra función readUser(json) no solo leerá JSON, sino que verificará (“validará”) los datos. Si no hay campos obligatorios, o el formato es incorrecto, entonces es un error. Y eso no es un “SyntaxError”, porque los datos son sintácticamente correctos, sino otro tipo de error. Lo llamaremos ValidationError y crearemos una clase para ello. Un error de ese tipo también debe llevar la información sobre el campo infractor.

Nuestra clase ValidationError debería heredar de la clase incorporada Error.

Esa clase está incorporada, pero aquí está su código aproximado para que podamos entender lo que estamos extendiendo:

// El "pseudocódigo" para la clase Error incorporada definida por el propio JavaScript
class Error {
  constructor(message) {
    this.message = message;
    this.name = "Error"; // (diferentes nombres para diferentes clases error incorporadas)
    this.stack = <call stack>; // no estándar, pero la mayoría de los entornos lo admiten
  }
}

Ahora heredemos ValidationError y probémoslo en acción: