La ley de Murphy afirma que todo lo que puede salir mal, acabará saliendo mal. Esto se aplica demasiado bien en el mundo de la programación. Si creas una aplicación, lo más probable es que se produzcan errores y otros problemas. Los errores en JavaScript son uno de esos problemas habituales

El éxito de un producto de software depende de lo bien que sus creadores puedan resolver estos problemas antes de perjudicar a sus usuarios. Y JavaScript, de entre todos los lenguajes de programación, es notorio por su diseño de gestión de errores mediocre.

Si estás construyendo una aplicación en JavaScript, hay muchas posibilidades de que metas la pata con los tipos de datos en un momento u otro. Si no es eso, puede que acabes sustituyendo un indefinido por un nulo o un operador de igualdad triple (===) por un operador de igualdad doble (==).

Es humano cometer errores. Por eso te mostraremos todo lo que necesitas saber sobre el manejo de errores en JavaScript.

Este artículo te guiará a través de los errores básicos en JavaScript y te explicará los distintos errores que puedes encontrar. A continuación, aprenderás a identificar y solucionar estos errores. También hay un par de consejos para manejar los errores con eficacia en entornos de producción.

Sin más preámbulos, ¡comencemos!

Mira Nuestro Videotutorial para Gestionar los Errores de JavaScript

¿Qué Son los Errores de JavaScript?

Los errores en programación se refieren a situaciones que no permiten que un programa funcione normalmente. Pueden ocurrir cuando un programa no sabe cómo manejar el trabajo que tiene en cuestión, como cuando intenta abrir un archivo inexistente o llegar a un punto final de la API basada en la web mientras no hay conectividad de red.

Estas situaciones que el programa lance errores al usuario, indicando que no sabe cómo proceder. El programa recoge toda la información posible sobre el error y luego informa de que no puede seguir adelante.

Los programadores inteligentes intentan predecir y cubrir estos escenarios para que el usuario no tenga que averiguar un mensaje de error técnico como «404» de forma independiente. En su lugar, muestran un mensaje mucho más comprensible «No se pudo encontrar la página».

Los errores en JavaScript son objetos que se muestran cada vez que se produce un error de programación. Estos objetos contienen amplia información sobre el tipo de error, la sentencia que lo ha provocado y el seguimiento de la pila cuando se ha producido el error. JavaScript también permite a los programadores crear errores personalizados para proporcionar información adicional al depurar problemas.

Propiedades de un Error

Ahora que la definición de un error de JavaScript está clara, es hora de entrar en los detalles.

Los errores en JavaScript conllevan ciertas propiedades estándar y personalizadas que ayudan a comprender la causa y los efectos del error. Por defecto, los errores en JavaScript contienen tres propiedades:

  1. mensaje: Un valor de cadena que lleva el mensaje de error
  2. nombre: El tipo de error que se ha producido (nos adentraremos en esto en la siguiente sección)
  3. pila: El rastro de la pila del código ejecutado cuando se produjo el error.

Además, los errores también pueden llevar propiedades como columnNumber, lineNumber, fileName, etc., para describir mejor el error. Sin embargo, estas propiedades no son estándar y pueden o no estar presentes en todos los objetos de error generados desde tu aplicación JavaScript.

Comprender el Seguimiento de la Pila (Stack Trace)

Un seguimiento de la pila es la lista de llamadas a métodos en la que se encontraba un programa cuando se produce un evento como una excepción o una advertencia. Este es el aspecto de un ejemplo de rastreo de pila acompañado de una excepción:

The error “TypeError: Numeric argument is expected” is shown on a gray background with additional stack details.
Ejemplo de un Stack Trace.

Como puedes ver, empieza imprimiendo el nombre y el mensaje del error, seguido de una lista de los métodos a los que se ha llamado. Cada llamada a un método indica la ubicación de su código fuente y la línea en la que fue invocado. Puedes utilizar estos datos para navegar por tu código base e identificar qué parte del código está causando el error.

Esta lista de métodos está ordenada de forma apilada. Muestra dónde se lanzó primero tu excepción y cómo se propagó a través de las llamadas a métodos apilados. Implementar una captura para la excepción no permitirá que se propague por la pila y que se bloquee tu programa. Sin embargo, es posible que quieras dejar sin atrapar los errores fatales para que el programa se bloquee en algunos escenarios de manera intencionada.

Errores vs. Excepciones

La mayoría de la gente suele considerar que los errores y las excepciones son lo mismo. Sin embargo, es esencial tener en cuenta una ligera, pero fundamental diferencia entre ellos.

Para entenderlo mejor, pongamos un ejemplo rápido. Así es como se puede definir un error en JavaScript:

const wrongTypeError = TypeError("Wrong type found, expected character")

Y así es como el objeto wrongTypeError se convierte en una excepción:

throw wrongTypeError

Sin embargo, la mayoría de la gente tiende a utilizar la forma abreviada que define los objetos de error mientras los lanza:

throw TypeError("Wrong type found, expected character")

Se trata de una práctica habitual. Sin embargo, es una de las razones por las que los desarrolladores tienden a confundir las excepciones y los errores. Por lo tanto, conocer los fundamentos es vital aunque utilices la forma abreviada para hacer tu trabajo rápidamente.

Tipos de Errores en JavaScript

Hay una serie de tipos de error predefinidos en JavaScript. Son elegidos y definidos automáticamente por el entorno de ejecución de JavaScript cuando el programador no maneja explícitamente los errores en la aplicación.

Esta sección te guiará a través de algunos de los tipos de error más comunes en JavaScript y entenderás cuándo y por qué se producen.

RangeError

Un RangeError se lanza cuando se establece una variable con un valor fuera de su rango de valores legales. Suele ocurrir cuando se pasa un valor como argumento a una función, y el valor dado no se encuentra en el rango de los parámetros de la función. A veces puede ser difícil de solucionar cuando se utilizan bibliotecas de terceros mal documentadas, ya que necesitas conocer el rango de valores posibles de los argumentos para pasar el valor correcto.

Algunas de las situaciones más comunes en las que se produce el RangeError son:

  • Intentar crear un array de longitudes ilegales mediante el constructor Array.
  • Pasar valores erróneos a métodos numéricos como toExponential(), toPrecision(), toFixed(), etc.
  • Pasando valores ilegales a funciones de cadena como normalize().

ReferenceError

Un ReferenceError se produce cuando algo está mal con la referencia de una variable en tu código. Puede que hayas olvidado definir un valor para la variable antes de usarla, o puede que estés intentando utilizar una variable inaccesible en tu código. En cualquier caso, revisar el seguimiento de la pila proporciona amplia información para encontrar y arreglar la referencia de la variable que está en mal estado.

Algunas de las razones más comunes por las que se producen errores de referencia son:

  • Escribir un error en el nombre de una variable.
  • Intentar acceder a variables de ámbito de bloque fuera de su ámbito.
  • Hacer referencia a una variable global de una biblioteca externa (como $ de jQuery) antes de que se cargue.

Error de Sintaxis

Estos errores son uno de los más sencillos de solucionar, ya que indican un error en la sintaxis del código. Dado que JavaScript es un lenguaje de scripting que se interpreta y no se compila, se lanzan cuando la aplicación ejecuta el script que contiene el error. En el caso de los lenguajes compilados, estos errores se identifican durante la compilación. Por lo tanto, los binarios de la aplicación no se crean hasta que se corrigen.

Algunas de las razones más comunes por las que pueden producirse SyntaxErrors son:

  • Faltan comillas
  • Falta de paréntesis de cierre
  • Alineación incorrecta de las llaves u otros caracteres

Es una buena práctica utilizar una herramienta de linting en tu IDE para identificar estos errores antes de que lleguen al navegador.

TypeError

TypeError es uno de los errores más comunes en las aplicaciones de JavaScript. Este error se produce cuando algún valor no resulta ser de un determinado tipo esperado. Algunos de los casos más comunes en los que se produce son:

  • Invocar objetos que no son métodos.
  • Intentar acceder a propiedades de objetos nulos o indefinidos
  • Tratar una cadena como un número o viceversa

Hay muchas más posibilidades en las que puede producirse un TypeError. Más adelante veremos algunos casos famosos y aprenderemos a solucionarlos.

InternalError

El tipo InternalError se utiliza cuando se produce una excepción en el motor de ejecución de JavaScript. Puede indicar o no un problema con tu código.

La mayoría de las veces, el InternalError solo se produce en dos casos:

  • Cuando un parche o una actualización del entorno de ejecución de JavaScript conlleva un error que lanza excepciones (esto ocurre muy raramente).
  • Cuando tu código contenga entidades demasiado grandes para el motor de JavaScript (por ejemplo, demasiados casos de conmutación, inicializadores de matrices demasiado grandes, demasiada recursión).

El enfoque más adecuado para resolver este error es identificar la causa a través del mensaje de error y reestructurar la lógica de tu aplicación, si es posible, para eliminar el repentino aumento de la carga de trabajo en el motor de JavaScript.

URIError

El URIError se produce cuando se utiliza de forma ilegal una función global de manejo de URI como decodeURIComponent. Suele indicar que el parámetro pasado a la llamada del método no se ajustaba a los estándares de URI y, por tanto, no fue analizado por el método correctamente.

Diagnosticar estos errores suele ser fácil, ya que solo tienes que examinar los argumentos para ver si están mal formados.

EvalError

Un EvalError se produce cuando se produce un error en una llamada a la función eval(). La función eval() se usa para ejecutar código JavaScript almacenado en cadenas. Sin embargo, como el uso de la función eval() está muy desaconsejado por cuestiones de seguridad y las especificaciones actuales de ECMAScript ya no lanzan la clase EvalError, este tipo de error existe simplemente para mantener la compatibilidad con el código JavaScript antiguo.

Si estás trabajando en una versión antigua de JavaScript, es posible que te encuentres con este error. En cualquier caso, lo mejor es investigar el código ejecutado en la llamada a la función eval() para ver si hay alguna excepción.

Crear Tipos de Error Personalizados

Aunque JavaScript ofrece una lista adecuada de clases de tipos de error para cubrir la mayoría de los escenarios, siempre puedes crear un nuevo tipo de error si la lista no satisface tus necesidades. La base de esta flexibilidad reside en el hecho de que JavaScript te permite lanzar literalmente cualquier cosa con el comando throw.

Así que, técnicamente, estas declaraciones son totalmente legales:

throw 8
throw "An error occurred"

Sin embargo, lanzar un tipo de dato primitivo no proporciona detalles sobre el error, como su tipo, nombre o el seguimiento de la pila que lo acompaña. Para solucionar esto y estandarizar el proceso de gestión de errores, se ha proporcionado la clase Error. También se desaconseja utilizar tipos de datos primitivos al lanzar excepciones.

Puedes extender la clase Error para crear tu clase de error personalizada. Aquí tienes un ejemplo básico de cómo puedes hacerlo:

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

Y puedes utilizarlo de la siguiente manera:

throw ValidationError("Property not found: name")

Y puedes identificarlo utilizando la palabra clave instanceof:

try {
    validateForm() // code that throws a ValidationError
} catch (e) {
    if (e instanceof ValidationError)
    // do something
    else
    // do something else
}

Los 10 Errores más Comunes en JavaScript

Ahora que entiendes los tipos de error más comunes y cómo crear los tuyos propios, es el momento de ver algunos de los errores más comunes a los que te enfrentarás al escribir código JavaScript.

Consulta Nuestro Videotutorial Sobre Los Errores Más Comunes De JavaScript

1. Uncaught RangeError

Este error se produce en Google Chrome en varios escenarios. En primer lugar, puede ocurrir si llamas a una función recursiva y esta no termina. Puedes comprobarlo tú mismo en la consola de desarrollador de Chrome:

The error “Uncaught RangeError: Maximum call stack size exceeded” is shown on a red background beside a red cross icon with a recursive function’s code above it.
Ejemplo de RangeError con una llamada a una función recursiva.

Por lo tanto, para solucionar este error, asegúrate de definir correctamente los casos límite de tu función recursiva. Otra razón por la que se produce este error es si has pasado un valor que está fuera del rango de los parámetros de la función. Aquí tienes un ejemplo:

The error “Uncaught RangeError: toExponential() argument must be between 0 and 100” is shown on a red background beside a red cross icon with a toExponential() function call above it.
Ejemplo de RangeError con la llamada toExponential().

El mensaje de error suele indicar lo que está mal en tu código. Una vez que hagas los cambios, se resolverá.

num = 4. num.toExponential(2). Output: 4.00e+0.
Salida de la llamada a la función toExponential().

2. Uncaught TypeError: No se Puede Establecer la Propiedad

Este error se produce cuando estableces una propiedad en una referencia no definida. Puedes reproducir el problema con este código:

var list
list.count = 0

Esta es la salida que recibirás:

The error “Uncaught TypeError: Cannot set properties of undefined” is shown on a red background beside a red cross icon with a list.count = 0 assignment above it.
Ejemplo de TypeError.

Para solucionar este error, inicializa la referencia con un valor antes de acceder a sus propiedades. Así es como se ve cuando se arregla:

Setting list.count = 10 after initializing list with {} due to which the output is 10.
Cómo solucionar el TypeError.

3. Uncaught TypeError: No se Puede Leer la Propiedad

Este es uno de los errores más frecuentes en JavaScript. Este error se produce cuando intentas leer una propiedad o llamar a una función sobre un objeto no definido. Puedes reproducirlo muy fácilmente ejecutando el siguiente código en la consola de Chrome Developer:

var func
func.call()

Este es el resultado:

The error “Uncaught TypeError: Cannot read properties of undefined” is shown on a red background beside a red cross icon with func.call() above it.
Ejemplo de TypeError con función indefinida.

Un objeto indefinido es una de las muchas causas posibles de este error. Otra causa destacada de este problema puede ser una inicialización incorrecta del estado al renderizar la UI. Aquí tienes un ejemplo del mundo real de una aplicación React:

import React, { useState, useEffect } from "react";

const CardsList = () => {

    const [state, setState] = useState();

    useEffect(() => {
        setTimeout(() => setState({ items: ["Card 1", "Card 2"] }), 2000);
    }, []);

    return (
        <>
            {state.items.map((item) => (
                <li key={item}>{item}</li>
            ))}
        </>
    );
};

export default CardsList;

La aplicación comienza con un contenedor de estado vacío y se le proporcionan algunos elementos después de un retraso de 2 segundos. El retraso se pone para imitar una llamada de red. Incluso si tu red es superrápida, te encontrarás con un pequeño retraso debido al cual el componente se renderizará al menos una vez. Si intentas ejecutar esta aplicación, recibirás el siguiente error:

The error “undefined is not an object” is shown on a grey background.
TypeError stack trace in a browser.

Esto se debe a que, en el momento de la renderización, el contenedor de estado está indefinido; por tanto, no existe ninguna propiedad items sobre él. Solucionar este error es fácil. Solo tienes que proporcionar un valor inicial por defecto al contenedor de estado.

// ...
const [state, setState] = useState({items: []});
// ...

Ahora, tras el retraso establecido, tu aplicación mostrará una salida similar:

A bulleted list with two items reading "Card 1" and "Card 2".
Salida del código.

La solución exacta en tu código puede ser diferente, pero la esencia aquí es inicializar siempre tus variables correctamente antes de usarlas.

4. TypeError: ‘undefined’ no es un Objeto

Este error se produce en Safari cuando intentas acceder a las propiedades o llamar a un método de un objeto no definido. Puedes ejecutar el mismo código de arriba para reproducir el error tú mismo.

The error “TypeError: undefined is not an object” shown on a red background beside a red exclamation point icon with func.call() method call above it.
Ejemplo de TypeError con función indefinida.

La solución a este error también es la misma: asegúrate de que has inicializado tus variables correctamente y que no están indefinidas cuando se accede a una propiedad o método.

5. TypeError: null no es un Objeto

Esto es, de nuevo, similar al error anterior. Se produce en Safari, y la única diferencia entre los dos errores es que este se lanza cuando el objeto a cuya propiedad o método se accede es null en lugar de undefined. Puedes reproducirlo ejecutando el siguiente fragmento de código:

var func = null

func.call()

Esta es la salida que recibirás:

The "TypeError: null is not an object" error message, shown on a red background beside a red exclamation point icon.
Ejemplo de TypeError con la función null.

Dado que null es un valor establecido explícitamente a una variable y no asignado automáticamente por JavaScript. Este error solo puede producirse si intentas acceder a una variable que hayas establecido tú mismo en null. Por lo tanto, debes revisar tu código y comprobar si la lógica que has escrito es correcta o no.

6. TypeError: No se Puede Leer la Propiedad ‘length’

Este error se produce en Chrome cuando intentas leer la longitud de un objeto null o undefined. La causa de este problema es similar a la de los problemas anteriores, pero se produce con bastante frecuencia al manejar listas, por lo que merece una mención especial. A continuación te explicamos cómo puedes reproducir el problema:

The error “Uncaught TypeError: Cannot read property 'length' of undefined” shown on a red background beside a red cross icon with myButton.length call above it.
Ejemplo de TypeError con un objeto indefinido.

Sin embargo, en las nuevas versiones de Chrome, este error se reporta como Uncaught TypeError: Cannot read properties of undefined. Así es como se ve ahora:

The error “Uncaught TypeError: Cannot read properties of undefined” shown on a red background beside a red cross icon with myButton.length call above it.
Ejemplo de TypeError con un objeto indefinido en las versiones más recientes de Chrome.

La solución, de nuevo, es asegurarse de que el objeto al que intentas acceder existe y no está configurado como null.

7. TypeError: ‘undefined’ no es una Función

Este error se produce cuando intentas invocar un método que no existe en tu script, o que sí existe, pero no puede ser referenciado en el contexto de llamada. Este error suele producirse en Google Chrome, y puedes solucionarlo verificando la línea de código que arroja el error. Si encuentras un error tipográfico, arréglalo y comprueba si se soluciona el problema.

Si has utilizado la palabra clave de autorreferencia this en tu código, este error puede surgir si this no está adecuadamente vinculado a tu contexto. Considera el siguiente código:

function showAlert() {
    alert("message here")
}

document.addEventListener("click", () => {
    this.showAlert();
})

Si ejecutas el código anterior, se producirá el error que hemos comentado. Esto ocurre porque la función anónima pasada como oyente de eventos se está ejecutando en el contexto de document.

En cambio, la función showAlert está definida en el contexto del window.

Para solucionarlo, debes pasar la referencia adecuada a la función vinculándola con el método bind():

document.addEventListener("click", this.showAlert.bind(this))

8. ReferenceError: el Evento no Está Definido

Este error se produce cuando intentas acceder a una referencia no definida en el ámbito de llamada. Esto suele ocurrir cuando se manejan eventos, ya que a menudo te proporcionan una referencia llamada event en la función de devolución de llamada. Este error puede producirse si te olvidas de definir el argumento del evento en los parámetros de tu función o lo escribes mal.

Este error puede no producirse en Internet Explorer o Google Chrome (ya que IE ofrece una variable de evento global y Chrome adjunta la variable de evento automáticamente al manejador), pero sí puede producirse en Firefox. Así que es aconsejable estar atento a estos pequeños errores.

9. TypeError: Asignación a una Variable Constante

Este es un error que surge por un descuido. Si intentas asignar un nuevo valor a una variable constante, te encontrarás con este resultado:

The error “Uncaught TypeError: Assignment to constant variable” shown on a red background beside a red cross icon with func = 6 assignment above it.
Ejemplo de TypeError con asignación de objeto constante.

Aunque ahora parece fácil de arreglar, ¡imagina cientos de declaraciones de variables de este tipo y que una de ellas se defina por error como const en lugar de let! A diferencia de otros lenguajes de scripting como PHP, la diferencia entre el estilo de declaración de constantes y variables en JavaScript es mínima. Por lo tanto, es aconsejable que compruebes tus declaraciones en primer lugar cuando te encuentres con este error. También puedes encontrarte con este error si olvidas que dicha referencia es una constante y la utilizas como una variable. Esto indica un descuido o un fallo en la lógica de tu aplicación. Asegúrate de verificarlo cuando intentes solucionar este problema.

10. (unknown): Error de Script

Un error de script se produce cuando un script de terceros envía un error a tu navegador. Este error va seguido de (desconocido) porque el script de terceros pertenece a un dominio diferente al de tu aplicación. El navegador oculta otros detalles para evitar la filtración de información sensible del script de terceros.

No puedes resolver este error sin conocer todos los detalles. Esto es lo que puedes hacer para obtener más información sobre el error:

  1. Añade el atributo crossorigin en la etiqueta script.
  2. Establece la cabecera Access-Control-Allow-Origin correcta en el servidor que aloja el script.
  3. [Opcional] Si no tienes acceso al servidor que aloja el script, puedes considerar la posibilidad de utilizar un proxy para retransmitir tu petición al servidor y devolverla al cliente con las cabeceras correctas.

Una vez que puedas acceder a los detalles del error, podrás ponerte manos a la obra para solucionar el problema, que probablemente estará relacionado con la biblioteca de terceros o con la red.

Cómo Identificar y Prevenir Errores en JavaScript

Aunque los errores comentados anteriormente son los más comunes y frecuentes en JavaScript con los que te encontrarás, basarse en algunos ejemplos nunca es suficiente. Es vital entender cómo detectar y prevenir cualquier tipo de error en una aplicación JavaScript mientras la desarrollas. A continuación te explicamos cómo puedes manejar los errores en JavaScript.

Lanzar y Atrapar Errores Manualmente

La forma más fundamental de manejar los errores lanzados manualmente o por el entorno de ejecución es atraparlos. Como la mayoría de los lenguajes, JavaScript ofrece un conjunto de palabras clave para manejar los errores. Es esencial que conozcas cada una de ellas en profundidad antes de ponerte a gestionar errores en tu aplicación JavaScript.

throw

La primera y más básica palabra clave del conjunto es throw. Como es evidente, la palabra clave throw se utiliza para lanzar errores y crear excepciones en el entorno de ejecución de JavaScript de forma manual. Ya hemos hablado de esto anteriormente en este artículo, y aquí está la esencia del significado de esta palabra clave:

  • Puedes hacer throw a cualquier cosa, incluyendo números, cadenas y objetos Error.
  • Sin embargo, no es aconsejable lanzar tipos de datos primitivos como cadenas y números, ya que no llevan información de depuración sobre los errores.
  • Ejemplo throw TypeError("Please provide a string")

try

La palabra clave try se usa para indicar que un bloque de código puede lanzar una excepción. Su sintaxis es:

try {
    // error-prone code here
}

Es importante tener en cuenta que un bloque catch debe seguir siempre al bloque try para manejar los errores con eficacia.

catch

La palabra clave catch se usa para crear un bloque catch. Este bloque de código se encarga de gestionar los errores que el bloque try atrapa. Esta es su sintaxis:

catch (exception) {
    // code to handle the exception here
}

Y así es como se implementan los bloques try y catch juntos:

try {
    // business logic code
} catch (exception) {
    // error handling code
}

A diferencia de C++ o Java, en JavaScript no puedes añadir varios bloques catch a un bloque try. Esto significa que no puedes hacer esto:

try {
    // business logic code
} catch (exception) {
    if (exception instanceof TypeError) {
        // do something
    }
} catch (exception) {
    if (exception instanceof RangeError) {
    // do something
    }
}

En su lugar, puedes utilizar una sentencia if...else o una sentencia switch case dentro del bloque catch único para manejar todos los posibles casos de error. El aspecto sería el siguiente:

try {
    // business logic code
} catch (exception) {
    if (exception instanceof TypeError) {
        // do something
    } else if (exception instanceof RangeError) {
        // do something else
    }
}

finally

La palabra clave finally se usa para definir un bloque de código que se ejecuta después de gestionar un error. Este bloque se ejecuta después de los bloques try y catch.

Además, el bloque finally se ejecutará independientemente del resultado de los otros dos bloques. Esto significa que aunque el bloque catch no pueda manejar el error por completo o se lance un error en el bloque catch, el intérprete ejecutará el código en el bloque finally antes de que el programa se bloquee.

Para que se considere válido, el bloque try en JavaScript debe ir seguido de un bloque catch o de un bloque finally. Sin ninguno de ellos, el intérprete lanzará un SyntaxError. Por lo tanto, asegúrate de seguir tus bloques try con al menos uno de ellos cuando manejes los errores.

Maneja los Errores Globalmente con el Método onerror()

El método onerror() está disponible para todos los elementos HTML para manejar cualquier error que pueda ocurrir con ellos. Por ejemplo, si una etiqueta img no puede encontrar la imagen cuya URL se especifica, dispara su método onerror para permitir al usuario manejar el error.

Lo normal es que proporciones otra URL de imagen en la llamada onerror para qué la etiqueta img pueda recurrir a ella. Así es como puedes hacerlo mediante JavaScript:

const image = document.querySelector("img")

image.onerror = (event) => {
    console.log("Error occurred: " + event)
}

Sin embargo, puedes utilizar esta función para crear un mecanismo global de gestión de errores para tu aplicación. Así es como puedes hacerlo:

window.onerror = (event) => {
    console.log("Error occurred: " + event)
}

Con este manejador de eventos, puedes deshacerte de los múltiples bloques try...catch que hay en tu código y centralizar el manejo de errores de tu aplicación de forma similar al manejo de eventos. Puedes adjuntar varios manejadores de errores a la ventana para mantener el principio de responsabilidad única de los principios de diseño SOLID. El intérprete recorrerá todos los manejadores hasta llegar al apropiado.

Pasar los Errores Mediante Devoluciones de Llamada

Mientras que las funciones simples y lineales permiten que el manejo de errores siga siendo sencillo, las devoluciones de llamada pueden complicar el asunto.

Considera el siguiente fragmento de código:

const calculateCube = (number, callback) => {
    setTimeout(() => {
        const cube = number * number * number
        callback(cube)
    }, 1000)
}

const callback = result => console.log(result)

calculateCube(4, callback)

La función anterior demuestra una condición asíncrona en la que una función se toma un tiempo para procesar las operaciones y devuelve el resultado más tarde con la ayuda de una devolución de llamada.

Si intentas introducir una cadena en lugar de 4 en la llamada a la función, obtendrás como resultado NaN.

Esto debe manejarse adecuadamente. He aquí cómo hacerlo:

const calculateCube = (number, callback) => {

    setTimeout(() => {
        if (typeof number !== "number")
            throw new Error("Numeric argument is expected")

        const cube = number * number * number
        callback(cube)
    }, 1000)
}

const callback = result => console.log(result)

try {
    calculateCube(4, callback)
} catch (e) { console.log(e) }

Esto debería resolver el problema idealmente. Sin embargo, si intentas pasar una cadena a la llamada de la función, recibirás esto:

The error “Uncaught Error: Numeric argument is expected” shown on a dark red background beside a red cross icon.
Ejemplo de error con un argumento equivocado.

Aunque hayas implementado un bloque try-catch al llamar a la función, sigue diciendo que el error es uncaught. El error se lanza después de que se haya ejecutado el bloque catch debido al retardo del tiempo de espera.

Esto puede ocurrir rápidamente en las llamadas a la red, donde se producen retrasos inesperados. Es necesario que cubras estos casos mientras desarrollas tu aplicación.

A continuación te explicamos cómo puedes manejar los errores correctamente en las devoluciones de llamada:

const calculateCube = (number, callback) => {

    setTimeout(() => {
        if (typeof number !== "number") {
            callback(new TypeError("Numeric argument is expected"))
            return
        }
        const cube = number * number * number
        callback(null, cube)
    }, 2000)
}

const callback = (error, result) => {
    if (error !== null) {
        console.log(error)
        return
    }
    console.log(result)
}

try {
    calculateCube('hey', callback)
} catch (e) {
    console.log(e)
}

Ahora, la salida en la consola será:

The message “TypeError: Numeric argument is expected” shown on a dark grey background.
Ejemplo de TypeError con argumento ilegal.

Esto indica que el error se ha gestionado adecuadamente.

Manejar los Errores en las Promesas

La mayoría de la gente tiende a preferir las promesas para manejar las actividades asíncronas. Las promesas tienen otra ventaja: una promesa rechazada no termina tu script. Sin embargo, tienes que implementar un bloque catch para gestionar los errores en las promesas. Para entenderlo mejor, reescribamos la función calculateCube() utilizando promesas:

const delay = ms => new Promise(res => setTimeout(res, ms));

const calculateCube = async (number) => {
    if (typeof number !== "number")
        throw Error("Numeric argument is expected")
    await delay(5000)
    const cube = number * number * number
    return cube
}

try {
    calculateCube(4).then(r => console.log(r))
} catch (e) { console.log(e) }

El tiempo de espera del código anterior se ha aislado en la función delay para que se entienda. Si intentas introducir una cadena en lugar de 4, la salida que obtendrás será similar a esta

The error “Uncaught (in promise) Error: Numeric argument is expected” shown on a dark grey background beside a red cross icon.
Ejemplo de TypeError con un argumento ilegal en Promise.

De nuevo, esto se debe a que Promise lanza el error después de que todo lo demás haya terminado de ejecutarse. La solución a este problema es sencilla. Basta con añadir una llamada a catch() a la cadena de la promesa, de la siguiente manera:

calculateCube("hey")
.then(r => console.log(r))
.catch(e => console.log(e))

Ahora la salida será:

The message “Error: Numeric argument is expected” shown on a dark grey background.
Ejemplo de TypeError con argumento ilegal

Puedes observar lo fácil que es manejar errores con promesas. Además, puedes encadenar un bloque finally() y la llamada a la promesa para añadir código que se ejecutará una vez finalizada la gestión de errores.

Alternativamente, también puedes manejar los errores en las promesas utilizando la técnica tradicional de try-catch-finally. Así es como se vería tu llamada a la promesa en ese caso:

try {
    let result = await calculateCube("hey")
    console.log(result)
} catch (e) {
    console.log(e)
} finally {
    console.log('Finally executed")
}

Sin embargo, esto solo funciona dentro de una función asíncrona. Por lo tanto, la forma preferida de manejar los errores en las promesas es encadenar catch y finally a la llamada a la promesa.

throw/catch vs. onerror() vs. Callbacks vs. Promesas: ¿Cuál es el Mejor?

Con cuatro métodos a tu disposición, debes saber elegir el más adecuado en cada caso de uso. A continuación te explicamos cómo podéis decidir por vosotros mismos:

throw/catch

Emplearás este método la mayor parte del tiempo. Asegúrate de implementar condiciones para todos los posibles errores dentro de tu bloque catch, y recuerda incluir un bloque finally si necesitas ejecutar algunas rutinas de limpieza de memoria después del bloque try.

Sin embargo, demasiados bloques try/catch pueden hacer que tu código sea difícil de mantener. Si te encuentras en una situación así, tal vez quieras manejar los errores a través del manejador global o del método promise.

A la hora de decidir entre los bloques try/catch asíncronos y la promesa catch(), es aconsejable optar por los bloques try/catch asíncronos, ya que harán que tu código sea lineal y fácil de depurar.

onerror()

Es mejor utilizar el método onerror() cuando sepas que tu aplicación tiene que manejar muchos errores, y que pueden estar bien dispersos por toda la base de código. El método onerror te permite manejar los errores como si fueran un evento más de tu aplicación. Puedes definir varios manejadores de errores y adjuntarlos a la ventana de tu aplicación en la renderización inicial.

Sin embargo, también debes recordar que el método onerror() puede ser innecesariamente difícil de configurar en proyectos más pequeños con un alcance de error menor. Si estás seguro de que tu aplicación no arrojará demasiados errores, el método tradicional throw/catch te irá mejor.

Devoluciones de Llamadas y Promesas

El manejo de errores en las llamadas de retorno y las promesas difiere debido a su diseño y estructura de código. Sin embargo, si eliges entre estos dos antes de haber escrito tu código, lo mejor será optar por las promesas.

Esto se debe a que las promesas tienen una construcción incorporada para encadenar un bloque catch() y otro finally() para manejar los errores fácilmente. Este método es más fácil y limpio que definir argumentos adicionales/reutilizar argumentos existentes para gestionar los errores.

Haz un Seguimiento de los Cambios con los Repositorios Git

Muchos errores suelen surgir debido a errores manuales en el código base. Mientras desarrollas o depuras tu código, puedes acabar haciendo cambios innecesarios que pueden provocar la aparición de nuevos errores en tu base de código. Las pruebas automatizadas son una buena forma de mantener tu código en orden después de cada cambio. Sin embargo, solo puede decirte si algo está mal. Si no haces copias de seguridad frecuentes de tu código, acabarás perdiendo el tiempo intentando arreglar una función o un script que antes funcionaba bien.

Aquí es donde git juega su papel. Con una estrategia de commit adecuada, puedes utilizar tu historial de git como sistema de copia de seguridad para ver tu código a medida que evoluciona a través del desarrollo. Puedes navegar fácilmente a través de tus commits más antiguos y encontrar la versión de la función que funcionaba bien antes, pero que arroja errores después de un cambio no relacionado.

A continuación, puedes restaurar el código antiguo o comparar las dos versiones para determinar qué ha fallado. Las herramientas modernas de desarrollo web, como GitHub Desktop o GitKraken, te ayudan a visualizar estos cambios uno al lado del otro y a descubrir los errores rápidamente.

Un hábito que puede ayudarte a cometer menos errores es realizar revisiones de código cada vez que hagas un cambio significativo en tu código. Si trabajas en equipo, puedes crear un pull request y hacer que un miembro del equipo lo revise a fondo. Esto te ayudará a utilizar un segundo par de ojos para detectar cualquier error que se te haya escapado.

Mejores Prácticas para Gestionar los Errores en JavaScript

Los métodos mencionados anteriormente son adecuados para ayudarte a diseñar un enfoque sólido de gestión de errores para tu próxima aplicación de JavaScript. Sin embargo, sería mejor que tuvieras en cuenta algunas cosas mientras los implementas para obtener lo mejor de tu gestión de errores. Aquí tienes algunos consejos que te ayudarán.

1. Utiliza Errores Personalizados Cuando Manejes Excepciones Operativas

Hemos introducido los errores personalizados al principio de esta guía para darte una idea de cómo personalizar el manejo de errores al caso único de tu aplicación. Es aconsejable utilizar errores personalizados siempre que sea posible en lugar de la clase genérica Error, ya que proporciona más información contextual al entorno de llamada sobre el error.

Además, los errores personalizados te permiten moderar cómo se muestra un error al entorno de llamada. Esto significa que puedes elegir ocultar detalles específicos o mostrar información adicional sobre el error como y cuando quieras.

Puedes llegar a formatear el contenido del error según tus necesidades. Esto te da un mejor control sobre cómo se interpreta y maneja el error.

2. No te Tragues Ninguna Excepción

Incluso los desarrolladores más veteranos suelen cometer un error de novato: consumir niveles de excepciones en lo más profundo de su código.

Puedes encontrarte con situaciones en las que tienes un trozo de código cuya ejecución es opcional. Si funciona, genial; si no lo hace, no necesitas hacer nada al respecto.

En estos casos, suele ser tentador poner este código en un bloque try y adjuntarle un bloque catch vacío. Sin embargo, al hacer esto, dejarás ese trozo de código abierto para causar cualquier tipo de error y salirte con la tuya. Esto puede llegar a ser peligroso si tienes una base de código grande y muchas instancias de estas construcciones de gestión de errores pobres.

La mejor manera de gestionar las excepciones es determinar un nivel en el que se tratarán todas ellas y levantarlas hasta allí. Este nivel puede ser un controlador (en una aplicación con arquitectura MVC) o un middleware (en una aplicación tradicional orientada al servidor).

De este modo, sabrás dónde puedes encontrar todos los errores que se produzcan en tu app y elegir cómo resolverlos, aunque eso signifique no hacer nada al respecto.

3. Utiliza una Estrategia Centralizada para los Registros y las Alertas de Error

El registro de un error suele ser una parte integral de su gestión. Los que no desarrollan una estrategia centralizada para registrar los errores pueden perder información valiosa sobre el uso de su aplicación.

Los registros de eventos de una aplicación pueden ayudarte a averiguar datos cruciales sobre los errores y a depurarlos rápidamente. Si tienes configurados mecanismos de alerta adecuados en tu aplicación, puedes saber cuándo se produce un error en tu aplicación antes de que llegue a una gran parte de tu base de usuarios.

Es aconsejable utilizar un registrador pre-construido o crear uno que se adapte a tus necesidades. Puedes configurar este registrador para que gestione los errores en función de sus niveles (advertencia, depuración, información, etc.), y algunos registradores llegan incluso a enviar los registros a servidores de registro remotos inmediatamente. De este modo, puedes observar cómo se comporta la lógica de tu aplicación con los usuarios activos.

4. Notifica los Errores a los Usuarios de Forma Adecuada

Otro buen punto que debes tener en cuenta al definir tu estrategia de gestión de errores es tener en cuenta al usuario.

Todos los errores que interfieren en el funcionamiento normal de tu aplicación deben presentar una alerta visible al usuario para notificarle que algo ha ido mal, de modo que pueda intentar encontrar una solución. Si conoces una solución rápida para el error, como reintentar una operación o cerrar la sesión y volver a iniciarla, asegúrate de mencionarla en la alerta para ayudar a arreglar la experiencia del usuario en tiempo real.

En el caso de errores que no causan ninguna interferencia con la experiencia diaria del usuario, puedes considerar la posibilidad de suprimir la alerta y registrar el error en un servidor remoto para resolverlo más tarde.

5. Implementa un Middleware (Node.js)

El entorno Node.js admite middlewares para añadir funcionalidades a las aplicaciones de servidor. Puedes utilizar esta función para crear un middleware de gestión de errores para tu servidor.

La ventaja más importante de utilizar un middleware es que todos tus errores se gestionan de forma centralizada en un solo lugar. Puedes optar por activar/desactivar esta configuración con fines de prueba fácilmente.

A continuación te explicamos cómo puedes crear un middleware básico:

const logError = err => {
    console.log("ERROR: " + String(err))
}

const errorLoggerMiddleware = (err, req, res, next) => {
    logError(err)
    next(err)
}

const returnErrorMiddleware = (err, req, res, next) => {
    res.status(err.statusCode || 500)
       .send(err.message)
}

module.exports = {
    logError,
    errorLoggerMiddleware,
    returnErrorMiddleware
}

A continuación, puedes utilizar este middleware en tu aplicación de la siguiente manera:

const { errorLoggerMiddleware, returnErrorMiddleware } = require('./errorMiddleware')

app.use(errorLoggerMiddleware)

app.use(returnErrorMiddleware)

Ahora puedes definir una lógica personalizada dentro del middleware para manejar los errores adecuadamente. Ya no tienes que preocuparte de implementar construcciones individuales de gestión de errores en toda tu base de código.

6. Reinicia tu Aplicación para Gestionar los Errores de Programación (Node.js)

Cuando las aplicaciones Node.js se encuentran con errores de programación, no necesariamente lanzan una excepción e intentan cerrar la aplicación. Estos errores pueden incluir problemas derivados de los errores del programador, como un alto consumo de CPU, hinchazón de memoria o fugas de memoria. La mejor manera de manejarlos es reiniciar la aplicación de forma elegante, cerrándola a través del modo de clúster de Node.js o de una herramienta única como PM2. Esto puede asegurar que la aplicación no se bloquee tras la acción del usuario, presentando una experiencia de usuario terrible.

7. Atrapar Todas las Excepciones no Capturadas (Node.js)

Nunca puedes estar seguro de haber cubierto todos los posibles errores que puedan ocurrir en tu aplicación. Por lo tanto, es esencial que implementes una estrategia de reserva para atrapar todas las excepciones no capturadas de tu aplicación.

He aquí cómo puedes hacerlo:

process.on('uncaughtException', error => {
    console.log("ERROR: " + String(error))
    // other handling mechanisms
})

También puedes identificar si el error que se ha producido es una excepción estándar o un error operativo personalizado. En función del resultado, puedes salir del proceso y reiniciarlo para evitar un comportamiento inesperado.

8. Atrapar Todos los Rechazos de Promesas no Manejadas (Node.js)

De forma similar a como nunca puedes cubrir todas las posibles excepciones, existe una alta probabilidad de que se te escape el manejo de todos los posibles rechazos de promesas. Sin embargo, a diferencia de las excepciones, los rechazos de promesas no lanzan errores.

Por tanto, una promesa importante que haya sido rechazada podría pasar desapercibida como advertencia y dejar tu aplicación expuesta a la posibilidad de encontrarse con un comportamiento inesperado. Por lo tanto, es crucial implementar un mecanismo de retroceso para manejar el rechazo de promesas.

He aquí cómo puedes hacerlo:

const promiseRejectionCallback = error => {
    console.log("PROMISE REJECTED: " + String(error))
}

process.on('unhandledRejection', callback)

Resumen

Como en cualquier otro lenguaje de programación, los errores son bastante frecuentes y naturales en JavaScript. En algunos casos, incluso puedes necesitar lanzar errores intencionadamente para indicar la respuesta correcta a tus usuarios. Por ello, es fundamental entender su anatomía y sus tipos.

Además, debes estar equipado con las herramientas y técnicas adecuadas para identificar y evitar que los errores acaben con tu aplicación.

En la mayoría de los casos, una estrategia sólida para manejar los errores con una ejecución cuidadosa es suficiente para todo tipo de aplicaciones JavaScript.

¿Hay algún otro error de JavaScript que aún no hayas podido resolver? ¿Alguna técnica para manejar los errores de JS de forma constructiva? ¡Háznoslo saber en los comentarios de abajo!

Kumar Harsh

Kumar is a software developer and a technical author based in India. He specializes in JavaScript and DevOps. You can learn more about his work on his website.