Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

?id=6966df4c-0fea-44f8-a4c4-2d39aa404bdf

Los 10 principales errores de JavaScript analizando más de 1000 proyectos (y cómo evitarlos)

Nota: este artículo es un traducción de Top 10 JavaScript errors from 1000+ projects (and how to avoid them) de Jason Skowronsk en el blog de Rollbar, traducido con el permiso expreso de esta empresa especializada en herramientas para diagnóstico de errores en programación.

Para contribuir a nuestra comunidad de desarrolladores, hemos analizado nuestra base de datos de miles de proyectos y hemos hallado los 10 principales errores que se suelen producir en JavaScript. Vamos a mostrarte qué los causa y cómo evitar que sucedan. Si evitas estos "descuidos", te convertirás en un mejor desarrollador.

Rollbar recopila todos los errores de cada proyecto y resume cuántas veces se produjo cada uno de ellos. Lo hacemos agrupando los errores según sus huellas dactilares. En definitiva, agrupamos dos errores si el segundo es sólo una repetición del primero. Esto da a los usuarios una visión general de la información en lugar de un enorme y abrumador volcado de datos como el que verías en un archivo de registro.

Nos centramos en los errores que más probablemente te afectarán a ti y a tus usuarios. Para ello, clasificamos los errores por el número de proyectos que los han sufrido en distintas empresas. Si nos fijamos sólo en el número total de veces que ocurrió cada error, entonces los clientes de alto volumen podrían saturar el conjunto de datos con errores que no son relevantes para la mayoría de los lectores.

Aquí están los 10 principales errores de JavaScript:

Gráfico de barras con la frecuencia de los 10 principales errores

Cada error ha sido acortado para facilitar la lectura. Profundicemos en cada uno de ellos para determinar qué puede causarlo y cómo evitar su aparición.

1. TypeError no capturado: No puede leer la propiedad

Si eres un desarrollador de JavaScript, es probable que hayas visto este error más de lo que te gustaría admitir. Esto ocurre en Chrome cuando lees una propiedad o llamas a un método en un objeto que no está definido. Puedes verificarlo muy fácilmente en la consola de desarrollo de Chrome:

Consola de chrome mostrando este error

Esto puede ocurrir por muchas razones, pero una de las más comunes es la inicialización incorrecta del estado mientras se renderizan los componentes de la interfaz de usuario. Veamos un ejemplo de cómo esto puede ocurrir en una aplicación del mundo real. Escogeremos React, pero los mismos principios de inicialización incorrecta también se aplican a Angular, Vue o cualquier otro framework.

class Quiz extends Component {
  componentWillMount() {
    axios.get('/thedata').then(res => {
      this.setState({items: res.data});
    });
  }

  render() {
    return (
      <ul>
        {this.state.items.map(item =>
          <li key={item.id}>{item.name}</li>
        )}
      </ul>
    );
  }
}

Hay dos cosas importantes a tener en cuenta aquí:

  1. El estado de un componente (por ejemplo, this.state) empieza su ciclo de vida como undefined.
  2. Cuando se obtienen datos de forma asíncrona, el componente se renderizará al menos una vez antes de que se carguen los datos, independientemente de si se obtienen en el constructor, componentWillMount o componentDidMount. Cuando Quiz se renderiza por primera vez, this.state.items no está definido. Esto, a su vez, significa que ItemList obtiene elementos como undefined, y obtienes por tanto el error "TypeError no capturado: No se puede leer la propiedad 'map' de undefined" en la consola, como acabamos de ver.

Esto es fácil de arreglar. La forma más simple: inicializar el estado en el constructor con valores por defecto razonables.

class Quiz extends Component {
  // Added this:
  constructor(props) {
    super(props);

    // Asignar el propio estado, y un valor por defecto para "items"
    this.state = {
      items: []
    };
  }

  componentWillMount() {
    axios.get('/thedata').then(res => {
      this.setState({items: res.data});
    });
  }

  render() {
    return (
      <ul>
        {this.state.items.map(item =>
          <li key={item.id}>{item.name}</li>
        )}
      </ul>
    );
  }
}

Puede que el código exacto de tu aplicación sea diferente, pero esperamos que te hayamos dado una pista lo suficientemente clara como para solucionar o evitar este problema en tus desarrollos. Si no es así, sigue leyendo porque a continuación veremos más ejemplos de errores relacionados.

2. TypeError: 'undefined' no es un objeto (evaluando...)

Este es un error que ocurre en Safari cuando lees una propiedad o llamas a un método en un objeto no definido. Puedes hacer esta prueba muy fácilmente en la consola de desarrollo de Safari. Este es básicamente el mismo error que el anterior para Chrome, pero Safari utiliza un mensaje de error diferente:

La consola de Safari mostrando este error

3. TypeError: null no es un objeto (evaluando...)

Este es un error que ocurre en Safari cuando lees una propiedad o llamas a un método en un objeto nulo. Puedes hacer esta prueba muy fácilmente en la consola de desarrollo de Safari:

La consola de Safari mostrando este error

Curiosamente, en JavaScript, null y undefined no son lo mismo, por lo que vemos dos mensajes de error diferentes. Algo indefinido es generalmente una variable que no ha sido asignada, mientras que nulo significa que el valor está en blanco. Para verificar que no son iguales, prueba a usar el operador de igualdad estricta:

Consola de Chrome mostrando que null y undefined no son iguales

Una forma en que este error puede ocurrir en un ejemplo del mundo real es si intentas usar un elemento DOM en tu JavaScript antes de que el elemento se cargue. Esto se debe a que la API DOM devuelve nulo para las referencias de objetos que están en blanco.

Cualquier código JS que ejecute y maneje elementos DOM debe ejecutarse después de que los elementos del DOM hayan sido creados. El código JS se interpreta de arriba hacia abajo como se haya dispuesto en el HTML. Por lo tanto, si hay una etiqueta <script> antes de los elementos DOM, el código JS dentro de esta etiqueta se ejecutará cuando el navegador analice la página HTML. Aparecerá este error si los elementos DOM no han sido creados antes de cargar el script.

En este ejemplo, podemos solucionar el problema añadiendo un manejador de eventos que nos notificará cuando la página esté lista. Una vez que el addEventListener se dispara, el método init() puede hacer uso de los elementos del DOM:

<script>
  function init() {
    var myButton = document.getElementById("myButton");
    var myTextfield = document.getElementById("myTextfield");
    myButton.onclick = function() {
      var userName = myTextfield.value;
    }
  }
  document.addEventListener('readystatechange', function() {
    if (document.readyState === "complete") {
      init();
    }
  });
</script>

<form>
  <input type="text" id="myTextfield" placeholder="Type your name" />
  <input type="button" id="myButton" value="Go" />
</form>

4. (desconocido): error de script

El error de script se produce cuando un error de JavaScript no gestionado adecuadamente cruza los límites del dominio e intenta violar la política de origen cruzado. Por ejemplo, si alojas tu código JavaScript en una CDN, cualquier error no capturado (errores que "burbujean" hasta el manejador global de errores en window.onerror, en lugar de ser atrapados en el try-catch) será reportado como simplemente "error de script", en lugar de contener información útil. Se trata de una medida de seguridad del navegador destinada a evitar el paso de datos a través de dominios, que no pueden comunicarse.

Para obtener los mensajes de error reales, haz lo siguiente:

1. Envía la cabecera Access-Control-Allow-Origin

Configurar la cabecera Access-Control-Allow-Origin en * significa que se puede acceder al recurso correctamente desde cualquier dominio. Puedes reemplazar * con tu dominio si es necesario: por ejemplo, Access-Control-Allow-Origin: www.ejemplo.com. Sin embargo, la gestión de varios dominios es complicada y puede que no valga la pena si utilizas una CDN, debido a los problemas de almacenamiento en caché que pueden surgir. Ver más aquí.

Aquí hay algunos ejemplos de cómo configurar esta cabecera en varios entornos:

Apache

En las carpetas desde las que se servirán los archivos JavaScript, crea un archivo .htaccess con el siguiente contenido:

Header add Access-Control-Allow-Origin "*"

Nginx

Añade la directiva add_header al bloque de ubicación que sirve los archivos JavaScript:

location ~ ^/assets/ {
    add_header Access-Control-Allow-Origin *;
}

HAProxy

Añade lo siguiente al backend desde donde se sirven los archivos JavaScript:

rspadd Access-Control-Allow-Origin:\ *

2. Define crossorigin="anonymous" en la etiqueta de script

En el código fuente HTML, para cada uno de los scripts para los que se haya establecido la cabecera Access-Control-Allow-Origin, define crossorigin="anonymous" en la etiqueta SCRIPT. Asegúrate de comprobar que la cabecera se está enviando para el archivo de script antes de añadir la propiedad crossorigin en esta etiqueta. En Firefox, si el atributo crossorigin está presente pero la cabecera Access-Control-Allow-Origin no lo está, el script no se ejecutará.

5. TypeError: el objeto no soporta la propiedad

Este es un error que se produce en Internet Explorer cuando se llama a un método que no se ha definido. Esto puede probarse en la IE Developer Console:

Consola de error de IE

Esto es equivalente al error TypeError: 'indefinido' no es una función en Chrome. Sí: diferentes navegadores pueden tener diferentes mensajes de error para el mismo error lógico.

Este es un problema habitual en IE en las aplicaciones web que utilizan el espacio de nombres JavaScript. Cuando es así, el problema, el 99,9% de las veces, es la incapacidad de IE para enlazar métodos dentro del espacio de nombres actual con la palabra clave this. Por ejemplo, si tienes el espacio de nombres de JS Rollbar con el método isAwesome. Normalmente, si nos encontramos en el espacio de nombres Rollbar, podemos invocar el método isAwesome con la siguiente sintaxis:

this.isAwesome();

Chrome, Firefox y Opera aceptarán gustosamente esta sintaxis. IE, en cambio, no lo hará. Por lo tanto, la apuesta más segura al usar el espaciado de nombres de JS es siempre prefijarlo con el espacio de nombres real.

Rollbar.isAwesome();

6. TypeError: 'undefined' no es una función

Este es un error que se produce en Chrome cuando se llama a una función que no está definida. Puedes hacer la prueba en las consolas Chrome Developer Console y Mozilla Firefox Developer Console.

Consola de Chrome mostrando este error

A medida que las técnicas de codificación JavaScript y los patrones de diseño se han vuelto cada vez más sofisticados con el paso de los años, ha habido un aumento correlativo en la proliferación en los ámbitos de auto-referenciación dentro de las llamadas de retorno (callbacks) y las clausuras, los cuales son una fuente bastante común de confusión entre this y that.

Consideremos este ejemplo de fragmento de código:

function clearBoard(){
  alert("Cleared");
}

document.addEventListener("click", function(){
  this.clearBoard(); //¿Qué es "this" aquí?
});

Si ejecutas el código anterior y luego haces clic en la página, se produce el siguiente error "TypeError no capturado: this.clearBoard no es una función". La causa es que la función anónima que se está ejecutando está en el contexto del documento, mientras que clearBoard se define en la ventana.

Una solución tradicional, compatible con navegadores antiguos, es simplemente guardar la referencia a this en una variable (that o self suelen ser nombres comunes) que luego puede ser heredada por el cierre. Por ejemplo:

var self=this;  // guardar una referencia a 'this', ¡mientras sigue siendo el this original!
document.addEventListener("click", function(){
  self.clearBoard();
});

Como alternativa, en los navegadores modernos, puedes usar el método bind() para pasar la referencia adecuada:

document.addEventListener("click",this.clearBoard.bind(this));

7. RangeError no capturado

Este es un error que ocurre en Chrome bajo un par de circunstancias. Una es cuando se llama a una función recursiva que no termina. Puedes hacer la prueba en la Chrome Developer Console.

Consola de Chrome con error al crear una recursión que no termina

También puede ocurrir si se le pasa un valor a una función que está fuera de rango. Muchas funciones aceptan sólo un rango específico de números para sus valores de entrada. Por ejemplo, Number.toExponential(digits) y Number.toFixed(digits) aceptan dígitos del 0 al 100, y Number.toPrecision(digits) aceptan dígitos del 1 al 100.

var a = new Array(4294967295);  //OK
var b = new Array(-1); //range error

var num = 2.555555;
document.writeln(num.toExponential(4));  //OK
document.writeln(num.toExponential(-2)); //range error!

num = 2.9999;
document.writeln(num.toFixed(2));   //OK
document.writeln(num.toFixed(105));  //range error!

num = 2.3456;
document.writeln(num.toPrecision(1));   //OK
document.writeln(num.toPrecision(0));  //range error!

8. TypeError: No puede leer la propiedad 'length'

Este es un error que ocurre en Chrome debido a la lectura de la propiedad length de una variable indefinida. Puedes probarlo en la Consola de Desarrolladores de Chrome.

Consola de Chrome mostrando el error

Normalmente la propiedad length se encuentra definida en una matriz, pero puede ocurrir este error si la matriz no está inicializada o si el nombre de la variable está oculto en otro contexto. Entendamos este error con el siguiente ejemplo.

var testArray= ["Test"];

function testFunction(testArray) {
    for (var i = 0; i < testArray.length; i++) {
      console.log(testArray[i]);
    }
}

testFunction();

Cuando se declara una función con parámetros, estos parámetros se convierten en variables locales. Esto significa que incluso si se tienen variables globales con nombres testArray, los parámetros con los mismos nombres dentro de una función seguirán considerándose como locales y ocultarán al valor global.

Tiene dos maneras de resolver el asunto:

1. Eliminar parámetros en la declaración de la función

Resulta que quieres acceder a aquellas variables que se declaran fuera de la función, por lo que no necesitas parámetros para tu función:

var testArray = ["Test"];

/* Precondición: que testArray esté definido fuera de la función */
function testFunction(/* No params */) {
   for (var i = 0; i < testArray.length; i++) {
     console.log(testArray[i]);
   }
}

testFunction();

aunque esto tiene un acoplamiento muy peligroso que deberíamos evitar.

2. Invocar a la función pasándole la matriz que hemos declarado:

var testArray = ["Test"];

function testFunction(testArray) {
  for (var i = 0; i < testArray.length; i++) {
     console.log(testArray[i]);
   }
}

testFunction(testArray);

que sería lo más normal.

9. TypeError no capturado: No se puede definir la propiedad

Cuando intentamos acceder a una variable indefinida siempre nos devuelve undefined, y no podemos obtener o definir ninguna propiedad de este valor undefined. En ese caso, una aplicación lanzará "TypeError no capturado: no se puede definir la propiedad de indefinida".

Por ejemplo, en el navegador Chrome:

Error provocado en Chrome para mostrar el mensaje

Si el objeto test no existe, el error lanzará TypeError no capturado: no se puede definir la propiedad de indefinida.

10. ReferenceError: evento no definido

Este error se produce cuando se intenta acceder a una variable que está indefinida o fuera del ámbito actual. Puedes hacer una prueba muy fácilmente en el navegador Chrome:

Mensaje de error de ejemplo

Si aparece este error al utilizar el sistema de gestión de eventos, asegúrate de utilizar el objeto de evento pasado como parámetro. Los navegadores más antiguos como IE ofrecen un evento de variable global, y Chrome automáticamente adjunta la variable de evento al manejador como un parámetro. Firefox no lo añadirá automáticamente. Las bibliotecas como jQuery intentan normalizar este comportamiento. Sin embargo, es una buena práctica usar la variable que se ha pasado a la función del manejador de eventos:

document.addEventListener("mousemove", function (event) {
  console.log(event);
})

Conclusión

Resulta que muchos de estos son errores de nulos o indefinidos. Utilizar un buen sistema de comprobación de tipos estático como el que incorpora Typescript podría ayudarte a evitarlos si utilizas la opción "strict" del compilador. Puede avisarte si se espera un determinado tipo en una variable pero no está definido. Incluso sin Typescript, es de gran ayuda usar cláusulas de protección para comprobar si los objetos están definidos antes de usarlos.

Esperamos que hayas aprendido algo nuevo y puedas evitar errores en el futuro, o que esta guía te haya ayudado a resolver problemas confusos. Sin embargo, incluso empleando buenas prácticas, surgen errores inesperados en producción. Es importante tener visibilización sobre los errores que afectan a los usuarios y disponer de buenas herramientas para resolverlos rápidamente.

Rollbar te da visibilidad a los errores de producción en JavaScript y te da más contexto para resolverlos rápidamente. Por ejemplo, ofrece funciones de depuración adicionales, como la telemetría, que te informa de lo que sucedió en el navegador del usuario que provocó el error. Eso es algo que no tienes fuera de tu consola de desarrollo local. Para más información, consulta la lista completa de funciones de Rollbar para aplicaciones JavaScript.

campusMVP campusMVP es la mejor forma de aprender a programar online y en español. En nuestros cursos solamente encontrarás contenidos propios de alta calidad (teoría+vídeos+prácticas) creados y tutelados por los principales expertos del sector. Nosotros vamos mucho más allá de una simple colección de vídeos colgados en Internet porque nuestro principal objetivo es que tú aprendas. Ver todos los posts de campusMVP
Archivado en: Desarrollo Web

No te pierdas nada, recibe lo mejor en tu email

Si te ha gustado este art­ículo, únete a miles de desarrolladores que ya reciben cada mes nuestro boletí­n por email. No te pierdas los mejores trucos, noticias y frikadas.

Enviamos poco, pero bueno. Palabra de desarrollador.

Suscríbete aquí­

Sí­guenos también en:

Telegram LinkedIn YouTube
La mejor formación online para desarrolladores como tú

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.