• ES2021 / ES12: novedades de la última versión de JavaScript

    7 de agosto de 2021

    - 5 min read

  • JavaScript no deja de evolucionar y todos los años se añaden nuevas funcionalidades para mejorar la productividad de los desarrolladores. Este año 2021 no iba a ser una excepción y te explico las nuevas características que ya tienes disponible en la mayoría de navegadores.

    1. Logical Assignment Operators (&&= ||= ??=)

    Los operadores lógicos &&, || y ?? ahora también pueden usarse para asignar valores de una forma más sencilla y corta. Perfecto para asignar valores por defecto a variables.

    // Si x es falsy, se le asigna y
    x ||= y;
    // Equivale a...
    x || (x = y);
    
    // Si x es truthy, se le asigna y
    x &&= y;
    // Equivale a...
    x && (x = y);
    
    // Si x es null o undefined, se le asigna y
    x ??= y;
    // Equivale a...
    x ?? (x = y);

    Hay que tener en cuenta que en estas asignaciones, además, entra el juego la evaluación short-circuit. Esto quiere decir que estas asignaciones lógicas se evaluan de izquierda a derecha. Si una expresión lógica no se cumple, no se evalúa la siguiente.

    Esto es importante para no cometer errores:

    // este nuevo tipo de asignación con &&
    x &&= y;
    // ✅ es equivalente a...
    x && (x = y);
    // ❌ NO es equivalente a...
    x = x && y;
    // ya que la asignación ocurre siempre independientemente de la evaluación

    2. Numeric Separator

    Leer algunas cifras en JavaScript puede ser una tarea difícil. Para solucionar esto, el nuevo separador numérico _ te permite identificar de manera más sencilla cualquier número.

    // Es difícil saber qué cifra representa
    1000000000;
    19436871.42;
    
    // ¡Con Numeric Separator es más fácil!
    1_000_000_000; // Ah, es mil millones
    100_000_000; // Y esto es cien millones
    19_436_871.42; // ¡De un vistazo!

    3. Promise.any

    ¿Alguna vez has querido esperar una lista de promesas y que, al resolverse correctamente una cualquiera, continuar con la ejecución de tu código? Pues para eso se incorpora Promise.any().

    const promises = [
      fetch("/from-external-api"),
      fetch("/from-memory"),
      fetch("/from-new-api"),
    ];
    
    try {
      // espera a la primera respuesta correcta que termine
      const first = await Promise.any(promises);
      // La más rápida fue la de memoria
      console.log(first); // respuesta desde 'from-memory'
    } catch (error) {
      // ¡Todas las promesas han fallado!
      console.assert(error instanceof AggregateError);
      // Log the rejection values:
      console.log(error.errors);
      // → [
      //     <TypeError: Failed to fetch /from-external-api>,
      //     <TypeError: Failed to fetch /from-memory>,
      //     <TypeError: Failed to fetch /from-new-api>
      //   ]
    }

    AggregateError

    Como has podido ver en el ejemplo anterior, ahora cuando la promesa falla, se devuelve una instancia de AggregateError. Este error es una instancia de Error y tiene una propiedad llamada errors que contiene una lista de errores para cada promesa que falló.

    La diferencia con Promise.race

    Promise.race y Promise.any son muy similares. La diferencia es que Promise.race se resuelve cuando cualquier promesa ha sido resuelta o rechazada. En cambio Promise.any ignora las promesas que son rechazadas y sólo se resuelve cuando se resuelve la primera… o se rechaza cuando todas las promesas se han rechazado.

    La tabla de diferencias de Promise

    Para que lo veas más claro, he preparado una pequeña tabla para diferenciar los diferentes métodos de Promise a la hora de trabajar con un array de promesas, para que eligas la que más encaje con tu caso de uso.

    +−−−−−−−−−−−−−−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−-−+−−−−−−−−−−−−−−−-−+
    | Método             | Descripción                                      | Añadida en...   |
    +−−−−−−−−−−−−−−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−-−+−−−−−−−−−−−−−−−−-+
    | Promise.allSettled | Espera a todas las promesas se resuelvan o no    | ES2020          |
    | Promise.all        | Se para cuando una promesa es rechazada          | ES2015          |
    | Promise.race       | Se para cuando una promesa es rechaza o resuelta | ES2015          |
    | Promise.any        | Se para cuando una promesa es resuelta           | ES2021          |
    +−−−−−−−−−−−−−−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−-−−−+−−−−−−−−−−−−−−−-−+

    4. replaceAll

    Hasta ahora, reemplazar todas las instancias de una cadena de texto en una cadena de texto te obligaba a usar Regex ya que replace, si le pasabas un string, lo que hacía era sólo reemplazar la primera instancia encontrada.

    // ¡Quiero cambiar las manzanas por bananas!
    "🍏🍏🍋🍋🍊🍊".replace("🍏", "🍌");
    // Pero qué...
    // -> '🍌🍏🍋🍋🍊🍊'
    
    // ¡Tienes que usar Regex para conseguirlo!
    "🍏🍏🍋🍋🍊🍊".replace(/🍏/g, "🍌");
    
    // ¡Hasta ahora! ¡Hola replaceAll!
    "🍏🍏🍋🍋🍊🍊".replaceAll("🍏", "🍌");

    replaceAll queda mucho más legible en nuestro código y hace justo lo que esperaba: cambiar todas las instancias de una cadena de texto en una cadena de texto.

    5. WeakRef

    WeakRef te permite crear una referencia débil a un objeto para no prevenir que se destruya por el Garbage Collector de JavaScript. ¿Por qué? Pues por qué cuando creamos un objeto, especialmente si son grandes, estos no son automáticamente destruidos por el Garbage Collector si existe una referencia a ellos.

    Con el método deref de WeakRef, podemos acceder a la referencia del objeto. Si la referencia al objeto ha sido eliminada, se devuelve undefined.

    // Al crear un objeto...
    let coords = { x: 13, y: 72 };
    // Mientras tengas acceso a él directamente,
    // el objeto no será liberado de memoria
    // por el Garbage Collector
    
    // Ahora podemos crear una referencia débil al objeto
    const weakCoords = new WeakRef(coords);
    
    // Recuperamos las propiedades del elemento
    const ref = weakCoords.deref();
    if (ref) {
      console.log("Todavía tenemos acceso a las coordenadas");
      ref.x; // -> 13
    } else {
      // ref es `undefined`
      console.log("La referencia ha sido eliminada");
    }

    Una cosa que debes tener en cuenta con WeakRef es que… seguramente es mejor si no lo usas. Esta funcionalidad está pensado para casos muy específicos que, en general, acabarán en librerías y frameworks. Está bien que conozcas su existencia pero los casos de uso son muy limitados. La recolección de basura en JavaScript puede variar mucho dependiendo del navegador, entorno y especificaciones del sistema.