6 características de JavaScript que debes conocer

Publicada en WordPress.

Mira nuestro vídeo

Existe una versión mejor de tu web

Comparte este artículo

Hace poco más de un año hablábamos de las 5 «novedades» de JavaScript que deberías conocer para desarrollar cómodamente en Gutenberg. Allí aprendimos qué es la destructuración de objetos y arrays, cómo crear funciones con flecha, qué son los operadores de propagación y resto, etc. Si eso te gustó, agárrate porque JavaScript sigue escondiendo un montón de características que, si las conocemos y aplicamos, nos ayudarán a escribir código más inteligible y sucinto.

Hoy te traigo otras seis features de JavaScript que molan un montón. Operadores, ámbito de variables, promises y funciones asíncronas… ¿estás preparado para aprender?

#1 Encadenamiento opcional en JavaScript con el operador «?.»

El operador de encadenamiento opcional permite acceder de forma simplificada a los atributos de un objeto cuando este puede ser undefined o null. Por ejemplo, supón que tienes un objeto como el siguiente:

const toni = {
  name: 'Antonio',
  details: {
    age: 35,
  },
};

Para acceder a la edad de toni debemos acceder primero al atributo details y luego a age:

function getAge( person ) {
  return person.details.age;
}

El problema que tenemos en la función anterior, y que seguro que has sufrido en tus carnes, es que no siempre te pasan un objeto válido. Si person o person.details es undefined, la función anterior devolvería un error:

getAge( toni ); // 35
getAge( {} );   // Uncaught TypeError: person.details is undefined

La forma habitual de evitar este problema es meter unas cuantas salvaguardas que comprueben la validez del objeto y eviten el error:

function getAge( person ) {
  if ( ! person ) {
    return;
  }
  if ( ! person.details ) {
    return;
  }
  return person.details.age;
}

getAge( toni ); // 35
getAge( {} );   // undefined

Esta solución, aunque efectiva, tiene un gran defecto: hemos pasado de algo sencillo a un «monstruo» lleno de comprobaciones previas que dificultan la inteligibilidad de nuestro código. Para simplificarlo, lo único que debemos hacer es usar el operador de encadenamiento opcional ?.:

function getAge( person ) {
  return person?.details?.age;
}

getAge( toni ); // 35
getAge( {} );   // undefined

con lo que nos ahorramos las salvaguardas y evitamos que el código pete si alguna de las propiedades intermedias es undefined o null.

#2 Operador de fusión nulo ??

En JavaScript es muy fácil poner un valor por defecto a una variable usando ||, ¿verdad? ¡Mentira! Todos lo hemos hecho, pero tiene efectos secundarios peligrosos…

Por ejemplo, supón que tenemos un selector en nuestro store de Redux que nos permite recuperar un valor definido por el usuario. Si el usuario aún no ha definido ningún valor, en el store lo mismo tenemos un undefined, con lo que podemos usar || para asignarle un valor por defecto:

function getValue( state ) {
  return state.value || 5;
}

Veamos qué valores nos devuelve la función según los setValue que haya hecho el usuario:

// No setValue( x )...
getValue(); // 5

setValue( 2 );
getValue(); // 2

setValue( 1 );
getValue(); // 1

setValue( 0 );
getValue(); // 5

¡Vaya! En el último caso, si el usuario especifica el valor 0, el resultado de la función es 5. El motivo es obvio: el operador || devuelve el segundo operando si el primero es un valor que evalúa a false. Nosotros lo usamos pensando que x || y devolvería y si la x era undefined o null, pero en realidad devuelve y si x es equivalente a false, y el 0 es un valor que es equivalente a usar false.

Para solucionar este problema, podemos aplicar la misma solución que antes y meter una salvaguarda explícita:

function getValue( state ) {
  if ( undefined === state.value ) return 5;
  return state.value;
}

o, en este caso, incluso colapsar la salvaguarda en un operador ternario ?::

function getValue( state ) {
  return undefined === state.value ? 5 : state.value;
}

pero ambas soluciones complican innecesariamente el código. Para asignar un valor por defecto a una variable, debemos usar el operador de fusión nulo ??, el cual devuelve la parte derecha si, y solo si, la izquierda es null o undefined:

function getValue( state ) {
  return state.value ?? 5;
}

con lo que, ahora sí, conseguimos el efecto deseado:

// No setValue( x )...
getValue(); // 5

setValue( 2 );
getValue(); // 2

setValue( 1 );
getValue(); // 1

setValue( 0 );
getValue(); // 0

Por cierto, este operador puede usarse, como la mayoría de los demás operadores, junto con el operador de asignación. Por ejemplo, esta expresión:

value = value ?? 5;

puede reescribirse tal que así:

value ??= 5;

#3 Promises y funciones asíncronas

Las promises son un mecanismo que permite simplificar nuestro código cuando trabajamos con operaciones asíncronas, esto es, operaciones cuyo resultado no podemos obtener de forma immediata. Por ejemplo, si queremos recuperar un dato de nuestro servidor, está claro que la respuesta no será instantánea, puesto que tenemos que esperar a que el servidor reciba la petición, la procese y nos envíe la respuesta.

En versiones anteriores de JavaScript (por ejemplo, con jQuery), esto solíamos implementarlo con callbacks. La idea era bastante sencilla: junto a la petición para recuperar el dato paso una función (el callback) que deberá llamarse cuando la respuesta esté disponible. De esta forma, cuando la operación asíncrona se complete, se llamará al callback y el código seguirá adelante:

jQuery.ajax( {
  url: 'https://server.com/wp-json/wp/users/1',
  success: ( user ) => {
    console.log( user );
  },
} );

Cuando se trata de una única petición, usar callbacks es una solución sencilla y relativamente elegante. El problema es que es muy fácil que degenere. Si queremos, por ejemplo, recuperar dos usuarios en lugar de uno, lo mismo empezamos a tener callbacks anidados dentro de otros callbacks:

jQuery.ajax( {
  url: 'https://server.com/wp-json/wp/v2/users/1',
  success: ( user1 ) => {
    jQuery.ajax( {
      url: 'https://server.com/wp-json/wp/v2/users/2',
      success: ( user2 ) => {
        console.log( user1, user2 );
      },
    } );
  },
} );

Para solucionar este problema aparecieron las promises. La idea detrás del concepto de promises es muy sencilla: en aquellos casos en los que obtener un resultado no es immediato (como, por ejemplo, cuando recuperamos algo del servidor), una promise en JavaScript es un objeto que sí podemos devolver al instante que sirve como «promesa» de que, en un futuro, el resultado estará disponible.

Por ejemplo, el primer código que hemos visto, escrito usando promises, sería tal que así:

const promise = wp.apiFetch( {
  url: 'https://server.com/wp-json/wp/v2/users/1',
} );
promise.then( ( user ) => console.log( user ) );

Como ves, la petición usando la librería wp.apiFetch devuelve una promise que podemos asignar directamente a una variable. Luego, lo único que tenemos que hacer es pasar un callback a la promise para recuperar el valor. Lo mismo estás pensando que esto no es muy diferente a lo que hacíamos hasta ahora… pero la ventaja es que, como te decía, nos permite ahorrar el código spaghetti:

const promise1 = wp.apiFetch( {
  url: 'https://server.com/wp-json/wp/v2/users/1',
} );
const promise2 = wp.apiFetch( {
  url: 'https://server.com/wp-json/wp/v2/users/2',
} );

Promise.all( [ promise1, promise2 ] ).then(
  ( [ user1, user2 ] ) => console.log( user1, user2 );
);

¿Has visto? Acabamos de lanzar dos peticiones en paralelo para recuperar los usuarios 1 y 2 y luego, usando Promise.all, hemos esperado a que ambas peticiones estuvieran resueltas para acceder a los resultados. No está nada mal, ¿eh?

Pues bien, JavaScript incluye una sintaxis adicional para trabajar con promises y código asíncrono como si fuera síncrono. Lo único que debes hacer es definir una función como asíncrona usando la palabra reservada async y, de repente, trabajar con operaciones asíncronas en su cuerpo se convierte en un juego de niños:

async function logTwoUsers( id1, id2 ) {
  const user1 = await wp.apiFetch( { url: '…' + id1 } );
  const user2 = await wp.apiFetch( { url: '…' + id2 } );
  console.log( user1, user2 );
}

Lo único que debes tener presente es que, cuando defines una operación como async, el resultado de la misma siempre, siempre, siempre será una promise (porque, por definición, una función asíncrona es aquella que, en general, no puede devolverte un resultado en seguida):

async function getNumberFive() {
  return 5;
}

const p = getNumberFive(); // a promise
p.then( console.log );     // prints "5"

#4 Ámbito de variables let y const

Este truquito es un poco menos impresionante que los anteriores, pero muy útil. Como ya sabes, en las nuevas versiones de JavaScript puedes declarar variables usando las palabras reservadas let y const. La primera define una variable a la que le puedes cambiar el valor y la segunda define una constante:

let x = 1;
console.log( x ); // 1
x = 2;
console.log( x ); // 2

const y = 1;
console.log( y ); // 1
y = 2;            // Uncaught TypeError: invalid assignment to const 'y'
console.log( y ); // 1

En principio puedes pensar que let es muy parecido al antiguo var, ¿no? Ambas palabras clave permiten declarar una variable cuyo valor podemos cambiar. Pero existe una diferencia sustancial entre ellos, y es el ámbito de aplicación de una variable. Con let y const, el ámbito de la variable es el bloque donde se define. En var, es la función entera.

function fn() {
  if ( true ) {
    var x = 1;
    let y = 2;
    const z = 3;
  }//end if
  console.log( x ); // 1
  console.log( y ); // Uncaught ReferenceError: y is not defined
  console.log( z ); // Uncaught ReferenceError: z is not defined
}

#5 Transformación de datos usando JSON.parse

La función JSON.parse permite procesar una cadena de texto en formato JSON y construir el objeto equivalente. Por ejemplo:

const x = JSON.parse( '{"x":1,"a":[1,2,3]}' );
// Object { x: 1, a: [ 1, 2, 3 ] }

Lo que mucha gente no sabe es que JSON.parse admite un segundo parámetro llamado reviver. Este parámetro es una función que va a ejecutarse para cada elemento que se está parseando y permite transformar el resultado. Por ejemplo, imagina un objeto JSON como el siguiente:

const json = '{"name":"David","birthday":"1985-12-01T10:00:00.000Z"}';

Si usamos JSON.parse tal cual, nos generará un objeto con dos atributos name y birthday de tipo string. Pero si le pasamos una función reviver a JSON.parse, podemos indicarle transformaciones adicionales como, por ejemplo, que birthday sea de tipo Date:

const user = JSON.parse(
  json,
  ( key, value ) => 'birthday' === key
    ? new Date( value )
    : value
)

#6 Separador de miles en el código fuente usando _

El último truco que te traigo hoy es el separador de miles en el código fuente. Hay una propuesta (actualmente, en fase 4) para permitir escribir los números en nuestro código fuente de una forma que sea mucho más inteligible para los humanos. Por ejemplo, ¿qué numeros ves aquí?

10000000000
2189719.25

Si pudiéramos usar el separador de miles, sería mucho más sencillo de interpretar, ¿verdad? Pues eso es precisamente lo que podemos hacer usando el guión bajo _:

10_000_000_000
2_189_719.25

Resumiendo

Como ves, es posible escribir mejor código JavaScript si te familiarizas con todas las herramientas y opciones que ofrece para ello. Hoy hemos visto varios ejemplos de lo que es posible con este lenguaje de programación amado y odiado a partes iguales.

Espero que hayas aprendido algo nuevo y, si te ha gustado, no olvides compartirlo con tus colegas. ¡Nos vemos pronto!

Imagen destacada de Sam Dan Truong en Unsplash.

Deja una respuesta

No publicaremos tu correo electrónico. Los campos obligatorios están marcados con: •

He leído y acepto la Política de privacidad de Nelio Software

Al marcar la casilla de aceptación estás dando tu legítimo consentimiento para que tu información personal se almacene en SiteGround y sea usada por Nelio Software con el propósito único de publicar aquí este comentario. Contáctanos para corregir, limitar, eliminar o acceder a tu información.