Para los desarrolladores de JavaScript, Lodash no necesita presentación. Sin embargo, la librería es vasta y a menudo se siente abrumadora. Ahora ya no

Lodash, Lodash, Lodash . . . ¡por dónde empiezo! 🤔

Hubo un tiempo en el que el ecosistema JavaScript era incipiente; podría compararse con el salvaje oeste o una jungla si se quiere, donde pasaban muchas cosas, pero había muy pocas respuestas para las frustraciones cotidianas de los desarrolladores y su productividad.

Entonces Lodash entró en escena, y se sintió como una inundación que lo sumergió todo. Desde simples necesidades cotidianas como la ordenación hasta complejas transformaciones de estructuras de datos, Lodash llegó cargado (¡sobrecargado, incluso!) de funcionalidades que convirtieron la vida de los desarrolladores JS en pura dicha.

¡Hola, Lodash!

¿Y dónde está Lodash hoy? Bueno, sigue teniendo todas las bondades que ofrecía al principio, y algunas más, pero parece haber perdido cuota de protagonismo en la comunidad JavaScript. ¿Por qué? Se me ocurren unas cuantas razones:

  • Algunas funciones de la biblioteca Lodash eran (y siguen siendo) lentas cuando se aplicaban a listas grandes. Aunque esto nunca habría afectado al 95% de los proyectos que existen, los desarrolladores influyentes del 5% restante dieron mala prensa a Lodash y el efecto se propagó en cascada hasta las bases.
  • Existe una tendencia en el ecosistema JS (incluso podría decirse lo mismo de la gente de Golang) en la que la arrogancia es más común de lo necesario. Así, confiar en algo como Lodash se ve como una estupidez y es derribado en foros como StackOverflow cuando la gente sugiere este tipo de soluciones («¡¿Qué?! ¿Usar una biblioteca entera para algo así? Puedo combinar filter() con reduce( ) para conseguir lo mismo en una simple función!»).
  • Lodash es antiguo. Al menos para los estándares de JS. Salió en 2012, por lo que en el momento de escribir esto, han pasado casi diez años. La API ha sido estable, y no se pueden añadir muchas cosas emocionantes cada año (simplemente porque no hay necesidad de hacerlo), lo que genera aburrimiento para el desarrollador JS medio sobreexcitado.

En mi opinión, no utilizar Lodash es una pérdida significativa para nuestras bases de código JavaScript. Ha demostrado no tener errores y ofrecer soluciones elegantes para los problemas cotidianos con los que nos encontramos en el trabajo, y utilizarlo sólo hará que nuestro código sea más legible y mantenible.

Dicho esto, sumerjámonos en algunas de las funciones comunes (¡o no!) de Lodash y veamos lo increíblemente útil y hermosa que es esta biblioteca.

Clone . . . ¡profundamente!

Dado que los objetos se pasan por referencia en JavaScript, se crea un dolor de cabeza para los desarrolladores cuando quieren clonar algo con la esperanza de que el nuevo conjunto de datos sea diferente.

let personas = [
  {
    nombre: 'Arnold',
    especialización: 'C ',
  },
  {
    nombre: 'Phil',
    especialización: 'Python',
  },
  {
    nombre: 'Percy',
    especialización: 'JS',
  },
];

// Encontrar gente escribiendo en C 
let genteHaciendoCpp = gente.filter((persona) => persona.especialización == 'C ');

// ¡Conviértalos a JS!
for (persona of genteHaciendoCpp) {
  persona.especialización = 'JS';
}

console.log(genteHaciendoCpp);
// [ { nombre: 'Arnold', especialización: 'JS' } ]

console.log(gente);
/*
[
  { nombre: 'Arnold', especialización: 'JS' }
  { nombre: 'Phil', especialización: 'Python' },
  { nombre: 'Percy', especialización: 'JS' }
]
*/

Observe cómo en nuestra pura inocencia y a pesar de nuestras buenas intenciones, la matriz de personas original mutó en el proceso (la especialización de Arnold cambió de C a JS) — ¡un duro golpe a la integridad del sistema de software subyacente! De hecho, necesitamos una forma de hacer una copia verdadera (profunda) del array original.

¡Hola Dave, te presento a Dave!

Quizás pueda argumentar que esta es una forma «tonta» de codificar en JS; sin embargo, la realidad es un poco complicada. Sí, disponemos del encantador operador de desestructuración, pero cualquiera que haya intentado desestructurar objetos y matrices complejas conoce el dolor. Luego, está la idea de utilizar la serialización y la de-serialización (quizás JSON) para lograr una copia profunda, pero esto sólo hace que su código sea más desordenado para el lector.

Por el contrario, mire lo asombrosamente elegante y concisa que es la solución cuando se utiliza Lodash:

const _ = require('lodash');

let gente = [
  {
    nombre: 'Arnold',
    especialización: 'C ',
  },
  {
    nombre: 'Phil',
    especialización: 'Python',
  },
  {
    nombre: 'Percy',
    especialización: 'JS',
  },
];

let genteCopia = _.cloneDeep(gente);

// Encontrar gente escribiendo en C 
let genteHaciendoCpp = genteCopia.filtrar(
  (persona) => persona.especialización == 'C '
);

// ¡Conviértalos a JS!
for (persona of genteHaciendoCpp) {
  persona.especialización = 'JS';
}

console.log(genteHaciendoCpp);
// [ { nombre: 'Arnold', especialización: 'JS' } ]

console.log(gente);
/*
[
  { nombre: 'Arnold', especialización: 'C ' }
  { nombre: 'Phil', especialización: 'Python' },
  { nombre: 'Percy', especialización: 'JS' }
]
*/

Observe cómo la matriz people permanece intacta tras la clonación profunda (Arnold sigue especializándose en C en este caso). Pero lo más importante es que el código es fácil de entender.

Eliminar duplicados de un array

Eliminar duplicados de una matriz suena como un excelente problema de entrevista/pizarra (recuerde, en caso de duda, ¡lance un hashmap al problema!). Y, por supuesto, siempre puede escribir una función personalizada para hacerlo, pero ¿y si se encuentra con varios escenarios diferentes en los que hacer que sus matrices sean únicas? Podría escribir varias otras funciones para eso (y arriesgarse a encontrarse con sutiles errores), ¡o podría simplemente utilizar Lodash!

Nuestro primer ejemplo de matrices únicas es bastante trivial, pero aún así representa la velocidad y fiabilidad que Lodash pone sobre la mesa. ¡Imagínese hacer esto escribiendo usted mismo toda la lógica personalizada!

const _ = require('lodash');

const userIds = [12, 13, 14, 12, 5, 34, 11, 12];
const uniqueUserIds = _.uniq(userIds);
console.log(uniqueUserIds);
// [ 12, 13, 14, 5, 34, 11 ]

Observe que el array final no está ordenado, lo cual, por supuesto, no es de ninguna preocupación aquí. Pero ahora, imaginemos un escenario más complicado: tenemos un array de usuarios que hemos sacado de algún sitio, pero queremos asegurarnos de que sólo contiene usuarios únicos. ¡Fácil con Lodash!

const _ = require('lodash');

const usuarios = [
  { id: 10, nombre: 'Phil', edad: 32 },
  { id: 8, nombre: 'Jason', edad: 44 },
  { id: 11, nombre: 'Rye', edad: 28 },
  { id: 10, nombre: 'Phil', edad: 32 },
];

const uniqueUsers = _.uniqBy(usuarios, 'id');
console.log(uniqueUsers);
/*
[
  { id: 10, nombre: 'Phil', edad: 32 },
  { id: 8, nombre: 'Jason', edad: 44 },
  { id: 11, nombre: 'Rye', edad: 28 }
]
*/

En este ejemplo, utilizamos el método uniqBy() para decirle a Lodash que queremos que los objetos sean únicos en la propiedad id. ¡En una línea, expresamos lo que podría haber tomado 10-20 líneas e introdujimos más margen para errores!

Hay mucho más material disponible sobre cómo hacer que las cosas sean únicas en Lodash, y le animo a que eche un vistazo a la documentación.

Diferencia de dos arrays

Unión, diferencia, etc., pueden sonar como términos que es mejor dejar atrás en las aburridas clases de bachillerato sobre Teoría de Conjuntos, pero aparecen más a menudo que no en la práctica diaria. Es habitual tener una lista y querer unir otra lista con ella o querer encontrar qué elementos son únicos en ella en comparación con otra lista; para estos escenarios, la función diferencia es perfecta.

Hola, A. ¡Adiós, B!

Empecemos el viaje de la diferencia con un escenario sencillo: ha recibido una lista de todos los id de usuario del sistema, así como una lista de aquellos cuyas cuentas están activas. ¿Cómo encuentra los id inactivos? Sencillo, ¿verdad?

const _ = require('lodash');

const allUserIds = [1, 3, 4, 2, 10, 22, 11, 8];
const activeUserIds = [1, 4, 22, 11, 8];

const inactiveUserIds = _.difference(allUserIds, activeUserIds);
console.log(inactiveUserIds);
// [ 3, 2, 10 ]

¿Y si, como ocurre en un entorno más realista, tiene que trabajar con un array de objetos en lugar de con primitivas simples? Bueno, ¡Lodash tiene un bonito método differenceBy( ) para esto!

const allUsers = [
  { id: 1, nombre: 'Phil' },
  { id: 2, nombre: 'John' },
  { id: 3, nombre: 'Rogg' },
];
const usuariosactivos = [
  { id: 1, nombre: 'Phil' },
  { id: 2, name: 'John' },
];
const inactiveUsers = _.differenceBy(allUsers, activeUsers, 'id');
console.log(usuariosinactivos);
// [ { id: 3, name: 'Rogg' } ]

Genial, ¿verdad?

Al igual que diferencia, existen otros métodos en Lodash para operaciones comunes con conjuntos: unión, intersección, etc.

Aplanar matrices

La necesidad de aplanar arrays surge con bastante frecuencia. Un caso de uso es que haya recibido una respuesta de la API y necesite aplicar algún combo map() y filter()en una lista compleja de objetos/arreglos anidados para arrancar, digamos, los id de usuario, y ahora le queden arreglos de arreglos. He aquí un fragmento de código que representa esta situación:

const ordenDatos = {
  interno: [
    { userId: 1, fecha: '2021-09-09', importe: 230.0, tipo: 'prepago' },
    { userId: 2, date: '2021-07-07', amount: 130.0, type: 'prepaid' },
  ],
  externo: [
    { userId: 3, date: '2021-08-08', amount: 30.0, tipo: 'postpago' },
    { userId: 4, date: '2021-06-06', amount: 330.0, type: 'postpaid' },
  ],
};

// encontrar ids de usuarios que realizaron pedidos de postpago (internos o externos)
const postpaidUserIds = [];

for (const [orderType, orders] of Object.entries(orderData)) {
  postpaidUserIds.push(orders.filter((order) => order.type === 'postpaid'));
}
console.log(postpaidUserIds);

¿Puede adivinar qué aspecto tiene ahora postPaidUserIds? Pista: ¡es asqueroso!

[
  [],
  [
    { userId: 3, date: '2021-08-08', amount: 30, tipo: 'postpago' },
    { userId: 4, date: '2021-06-06', amount: 330, tipo: 'postpago' }
  ]
]

Ahora bien, si es usted una persona sensata, no querrá escribir lógica personalizada para extraer los objetos de pedido y disponerlos bien en una fila dentro de un array. Simplemente utilice el método flatten() y disfrute de las uvas:

const flatUserIds = _.flatten(postpaidUserIds);
console.log(flatUserIds);
/*
[
  { userId: 3, date: '2021-08-08', amount: 30, tipo: 'postpago' },
  { userId: 4, date: '2021-06-06', amount: 330, tipo: 'postpago' }
]
*/

Tenga en cuenta que flatten ( ) sólo llega a un nivel de profundidad. Es decir, si sus objetos están atascados a dos, tres o más niveles de profundidad, flatten () le decepcionará. En esos casos, Lodash tiene el método flattenDeep( ), pero tenga en cuenta que aplicar este método en estructuras muy grandes puede ralentizar las cosas (ya que entre bastidores, hay una operación recursiva en funcionamiento).

¿Está vacío el objeto/matriz?

Gracias a cómo funcionan los valores y tipos «falsos» en JavaScript, a veces algo tan simple como comprobar si está vacío resulta en un pavor existencial.

¿Cómo puede comprobar si una matriz está vacía? Puede comprobar si su longitud es 0 o no. Ahora bien, ¿cómo puede comprobar si un objeto está vacío? Bueno… ¡un momento! Aquí es donde se instala esa sensación de inquietud, y esos ejemplos de JavaScript que contienen cosas como [] == false y {} == false empiezan a dar vueltas en nuestras cabezas. Cuando se está bajo presión para entregar una característica, minas terrestres como estas son lo último que necesita — harán que su código sea difícil de entender, e introducirán incertidumbre en su conjunto de pruebas.

Trabajar con datos que faltan

En el mundo real, los datos nos hacen caso; por mucho que los queramos, rara vez son ágiles y cuerdos. Un ejemplo típico son los objetos/arrays nulos que faltan en una gran estructura de datos recibida como respuesta de la API.

Supongamos que recibimos el siguiente objeto como respuesta de la API:

const apiResponse = {
  id: 33467,
  paymentRefernce: 'AEE3356T68',
  // falta el objeto `order
  processedAt: `2021-10-10 00:00:00`,
};

Como se muestra, generalmente obtenemos un objeto de pedido en la respuesta de la API, pero no siempre es así. Entonces, ¿qué pasa si tenemos algún código que depende de este objeto? Una forma sería codificar a la defensiva, pero dependiendo de lo anidado que esté el objeto de pedido, pronto estaríamos escribiendo un código muy feo si queremos evitar errores en tiempo de ejecución:

if (
  apiResponse.order &&
  apiResponse.order.payee &&
  apiResponse.order.payee.address
) {
  console.log(
    'El pedido se envió al código postal: ' 
      apiResponse.order.payee.address.zipCode
  );
}

🤢🤢 Sip, muy feo de escribir, muy feo de leer, muy feo de mantener, etc. Afortunadamente, Lodash tiene una forma sencilla de tratar estas situaciones.

const zipCode = _.get(apiResponse, 'order.payee.address.zipCode');
console.log('El pedido se envió al código postal: ' zipCode);
// El pedido se envió al código postal: undefined

También existe la fantástica opción de proporcionar un valor por defecto en lugar de obtener undefined para las cosas que faltan:

const zipCode2 = _.get(apiResponse, 'order.payee.address.zipCode', 'NA');
console.log('El pedido se envió al código postal: ' zipCode2);
// El pedido se envió al código postal: NA

No sé a usted, pero get() es una de esas cosas que me arrancan lágrimas de felicidad. No es nada llamativo. No hay sintaxis consultada ni opciones que memorizar, y sin embargo, ¡mire la cantidad de sufrimiento colectivo que puede aliviar! 😇

Desmontando

En caso de que no esté familiarizado, el debouncing es un tema común en el desarrollo frontend. La idea es que a veces es beneficioso lanzar una acción no inmediatamente sino después de algún tiempo (generalmente, unos pocos milisegundos). ¿Qué significa esto? He aquí un ejemplo.

Imagine un sitio web de comercio electrónico con una barra de búsqueda (bueno, ¡cualquier sitio web/aplicación web hoy en día!). Para mejorar la UX, no queremos que el usuario tenga que pulsar enter (o peor aún, pulsar el botón «buscar») para mostrar sugerencias/previsualizaciones basadas en su término de búsqueda. Pero la respuesta obvia está un poco cargada: si añadimos un oyente de eventos a onChange() para la barra de búsqueda y disparamos una llamada a la API por cada pulsación de tecla, habremos creado una pesadilla para nuestro backend; habrá demasiadas llamadas innecesarias (por ejemplo, si se busca «cepillo para alfombra blanca», ¡habrá un total de 18 peticiones!) y casi todas ellas serán irrelevantes porque la entrada del usuario no ha terminado.

La respuesta está en el debouncing, y la idea es la siguiente: no envíe una llamada a la API en cuanto cambie el texto. Espere un tiempo (digamos, 200 milisegundos) y si para entonces hay otra pulsación de tecla, cancele el recuento de tiempo anterior y vuelva a empezar a esperar. Como resultado, sólo cuando el usuario hace una pausa (ya sea porque está pensando o porque ha terminado y espera alguna respuesta) enviamos una solicitud de API al backend.

La estrategia general que he descrito es complicada, y no me sumergiré en la sincronización de la gestión del temporizador y la cancelación; sin embargo, el proceso real de depuración es muy sencillo si utiliza Lodash.

const _ = require('lodash');
const axios = require('axios');

// Por cierto, ¡esta es una API de perros real!
const fetchDogBreeds = () =>
  axios
    .get('https://dog.ceo/api/breeds/list/all')
    .then((res) => console.log(res.data));

const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 1000); // después de un segundo
debouncedFetchDogBreeds(); // muestra los datos después de un tiempo

Si está pensando que setTimeout () habría hecho el mismo trabajo, bueno, ¡hay más! El debounce de Lodash viene con muchas características poderosas; por ejemplo, podría querer asegurarse de que el debounce no sea indefinido. Es decir, aunque haya una pulsación de tecla cada vez que la función esté a punto de dispararse (cancelando así el proceso global), puede que quiera asegurarse de que la llamada a la API se realiza de todos modos después de, digamos, dos segundos. Para ello, Lodash debounce( ) dispone de la opción maxWait:

const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 150, { maxWait: 2000 }); // debounce durante 250ms, pero envíe la petición a la API después de 2 segundos de todos modos

Echa un vistazo a los documentos oficiales para una inmersión más profunda. ¡Están llenos de cosas superimportantes!

Eliminar valores de un array

No sé usted, pero yo odio escribir código para eliminar elementos de una matriz. Primero tengo que obtener el índice del elemento, comprobar si el índice es realmente válido y, en caso afirmativo, llamar al método splice(), y así sucesivamente. Nunca puedo recordar la sintaxis y, por tanto, tengo que buscar cosas todo el tiempo, y al final me quedo con la molesta sensación de que he dejado que se cuele algún estúpido error.

const saludos = ['hola', 'hola', 'hola', 'ola', 'hola'];
_.pull(saludos, 'ola', 'hola');
console.log(saludos);
// ['hola', 'hola']

Tenga en cuenta dos cosas:

  1. El array original se modificó en el proceso.
  2. El método pull( ) elimina todas las instancias, incluso si hay duplicados.

Existe otro método relacionado llamado pullAll () que acepta un array como segundo parámetro, lo que facilita la eliminación de varios elementos a la vez. Es cierto que podríamos simplemente utilizar pull( ) con un operador de dispersión, ¡pero recuerde que Lodash llegó en una época en la que el operador de dispersión ni siquiera era una propuesta en el lenguaje!

const saludos2 = ['hola', 'hola', 'hola', 'ola', 'hola'];
_.pullAll(saludos2, ['ola', 'hola']);
console.log(saludos2);
// ['hola', 'hola']

Último índice de un elemento

El método nativo indexOf() de JavsScript es genial, ¡excepto cuando le interesa escanear el array desde la dirección opuesta! Y de nuevo, sí, podría simplemente escribir un bucle decreciente y encontrar el elemento, pero ¿por qué no utilizar una técnica mucho más elegante?

He aquí una solución rápida de Lodash utilizando el método lastIndexOf():

const enteros = [2, 4, 1, 6, -1, 10, 3, -1, 7];
const index = _.lastIndexOf(integers, -1);
console.log(índice); // 7

Por desgracia, no existe ninguna variante de este método en la que podamos buscar objetos complejos o incluso pasar una función de búsqueda personalizada.

Zip. Descomprimir

A menos que haya trabajado en Python, zip/unzip es una utilidad que quizá nunca haya notado o imaginado en toda su carrera como desarrollador de JavaScript. Y quizá por una buena razón: rara vez hay el tipo de necesidad desesperada de zip/unzip que hay para filter(), etc. Sin embargo, es una de las mejores utilidades menos conocidas que existen y puede ayudarle a crear código sucinto en algunas situaciones.

Al contrario de lo que parece, zip/unzip no tiene nada que ver con la compresión. En su lugar, es una operación de agrupación en la que matrices de la misma longitud se pueden convertir en una única matriz de matrices con elementos en la misma posición empaquetados juntos(zip()) y de vuelta(unzip()). Sí, lo sé, se está volviendo confuso intentar arreglárselas con palabras, así que veamos algo de código:

const animales = ['pato', 'oveja'];
const tamaños = ['pequeño', 'grande'];
const peso = ['menor', 'mayor'];

const animalesagrupados = _.zip(animales, tamaños, peso);
console.log(animalesagrupados);
// [ [ 'pato', 'pequeño', 'menos' ], [ 'oveja', 'grande', 'más' ] ]

Las tres matrices originales se convirtieron en una sola con dos matrices solamente. Y cada una de estas nuevas matrices representa a un solo animal con todos sus into en un solo lugar. Así, el índice 0 nos dice qué tipo de animal es, el índice 1 nos dice su tamaño y el índice 2 nos dice su peso. Como resultado, ahora es más fácil trabajar con los datos. Una vez que haya aplicado las operaciones que necesite sobre los datos, puede volver a descomprimirlos con unzip( ) y enviarlos de vuelta a la fuente original:

const animalData = _.unzip(animalesagrupados);
console.log(animalData);
// [ [ 'pato', 'oveja' ], [ 'pequeño', 'grande' ], [ 'menos', 'más' ]

La utilidad zip/unzip no es algo que le vaya a cambiar la vida de la noche a la mañana, ¡pero algún día se la cambiará!

Conclusión 👨‍🏫

(¡He puesto aquí todo el código fuente utilizado en este artículo para que pueda probarlo directamente desde el navegador!)

La documentación de Lodash está repleta de ejemplos y funciones que le dejarán boquiabierto. En una época en la que el masoquismo parece aumentar en el ecosistema JS, Lodash es como un soplo de aire fresco, ¡y le recomiendo encarecidamente que utilice esta biblioteca en sus proyectos!