Componer tuberías (pipe) en Javascript

El concepto de tubería (del inglés pipe) es básicamente conectar múltiples procesos de tal forma que la salida del primero es la entrada del segundo; la salida del segundo es la entrada del tercero, etc. Este concepto puede ayudarte a crear un código más mantenible y legible. Te lo cuento con ejemplos.
Componer tuberías (pipe) en Javascript

El concepto de tubería o pipe es básicamente conectar múltiples funciones (o programas) de tal forma que la salida del primer comando es la entrada del segundo. La salida del segundo es la entrada del tercero, y así sucesivamente. Este concepto puede ayudarte a crear un código más mantenible y legible.

Qué es una tubería y porqué es útil

Las tuberías son tremendamente útiles en programación desde hace mucho tiempo, y si no que se lo digan a los usuarios de Linux. Y con el auge de la programación funcional, esta tendencia se está llevando a cada vez más lenguajes porque simplifican mucho el código, tanto para escribirlo como para leerlo. Para entendernos, el concepto de tubería en programación es como las tuberías que tienes en tu casa: conectar el final de un tramo al principio del otro tantas veces como sea necesario. Sin embargo, en programación lo que hacemos es unir el resultado que devuelve un método o función a la entrada de otra. De esta forma, cuando necesitamos aplicar una serie de procesos (cálculos o transformaciones por ejemplo) creamos una tubería que se encargará de que el dato original vaya pasando por la primera función; el resultado de la primera función será entregado como entrada de la segunda; la salida de la segunda se entregará a la tercera como entrada y así todas las veces que necesitemos. Cuando no haya más procesos conectados a dicha tubería, el resultado de la última función nos será devuelto y podremos dar el proceso (o suma de procesos mejor dicho) como terminado.

Empecemos con un ejemplo sin tubería

Lo vamos a ver mucho más claro con un ejemplo práctico que es posible que te haya pasado. Imagina que tenemos un dato, en este caso, un string que representa el nombre de usuario. El nombre del usuario no sabemos como nos llega (mayúsculas, minúsculas, signos) y por ello, queremos hacer una función que "limpie" y deje bonito el nombre de usuario para pintarlo en nuestra aplicación. Este proceso de limpieza se compone de los siguientes pasos:

  • Poner la primera letra en mayúsculas y el resto en minúsculas (capitalizar),
  • Eliminar los espacios en blanco,
  • Como máximo, solo se permiten 10 caracteres, así que cortamos a partir del 11 (truncar).

Las tres funciones anteriores las podemos declarar de cualquier forma, por ejemplo:

const capitalize = (string) => string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
const removeSpaces = (string) => string.replace(/\s/g, '');
const truncate = (string) => string.substring(0,10);

Hasta aquí nada nuevo, ¿no? Qué es lo que haríamos para devolver el nombre de usuario de forma "bonita", pues a primera vista crear una función tal que así:

const coolUsername = (name) => truncate(removeSpaces(capitalize(name)));

Como sabemos, se ejecuta primero el comando que está más dentro de las funciones. La salida de "capitalize" se pasa como entrada de "removeSpaces". Y a su vez la salida de "removeSpaces" se pasa como entrada de "truncate". Así tenemos nuestro nombre de usuario de la forma que nos han pedido. Y realmente no está mal, pero...

Para a pensar una cosa 🤔, ¿si el proceso de transformar el nombre de usuario en vez de tres funciones requiriese 5, 7, 9, 12 o 15 funciones? Encadenaríamos 15 funciones tal que "a(b(c(d(e(....)))))". El código sería una línea de muchos caracteres; los paréntesis de cierre serían imposibles de contar, habría que leerlo de derecha a izquierda... ¿No te empieza a parecer un código un poco feo?

Cómo usar tuberías o pipes en Javascript

Si has seguido leyendo es que estás de acuerdo conmigo en que debe haber una forma más cool 😎 de escribir el código. Y la respuesta es "Sí, con una tubería". Quédate con la siguiente línea. Luego la repasamos paso a paso:

const pipe = (...fns) => (arguments) => fns.reduce((value, fn) => fn(value), arguments);

Como sabes, en Javascript una función puede devolver otra función. Así que la función anterior "pipe" puede explicarse como una función que va ejecutando funciones (declaradas de izquierda a derecha) de tal forma que la salida de la anterior será la entrada de la siguiente mediante el método "reduce". Al final, la función "pipe" devolverá un valor "simple" que es el que nos interesa.

Con esto en mente, qué te parece redefinir nuestra función para limpiar el nombre de usuario de la siguiente manera:

const coolUsername = (nombreUsuario) =>
  pipe(
    capitalize,
    removeSpaces,
    truncate,
  )(nombreUsuario);

Más simple, se lee más rápido, se modifica más rápido si hiciese falta cambiar algún paso. Bueno, bonito y barato. Si te fijas, la parte donde se especifican las funciones que quieres ejecutar ("capitalize, removeSpaces, truncate") es el argumento primero de pipe llamado fns y "nombreUsuario" es el segundo argumento de pipe llamado arguments .

La ventaja de usar la función pipe que hemos definido es que puedes reutilizarla las veces que necesites para crear todas las tuberías que necesites: calcular un precio aplicando tasas o descuentos, procesar un array de alguna forma, lo que necesites. Además, por cómo es Javascript, puedes definir las funciones dentro de la propia tubería:

const calcularPrecio = (precio) =>
  pipe(
    sumarIVA,
    sumarOtrosImpuestos,
    precio => precio * 0.90, //Aplicar descuento custom.
  )(nombreUsuario);

Espero haberte ayudado a aprender un mecanismo interesante a la hora de crear funciones que se basan en aplicar de forma sistemática otras funciones. Usar tuberías es una forma de crear un código bueno, profesional y bastante limpio y legible.

¡Que tengas un feliz coding!