Uso práctico de curry en js usando el ejemplo del módulo de solicitud http

Hola a todos! No es ningún secreto para nadie que en el mundo de la programación hay muchas técnicas, prácticas y patrones de programación (diseño), pero a menudo, al aprender algo nuevo, no está completamente claro dónde y cómo aplicar esta nueva.


Hoy, utilizando el ejemplo de la creación de un pequeño módulo contenedor para trabajar con solicitudes http, analizaremos los beneficios reales del curry: la recepción de la programación funcional.


Para todos los recién llegados y aquellos interesados ​​en usar la programación funcional en la práctica, bienvenidos, aquellos que entienden perfectamente lo que es curry, espero sus comentarios sobre el código, porque como dicen, no hay límite para la perfección.


Así que empecemos


Pero no desde el concepto de curry, sino desde el enunciado del problema, donde podemos aplicarlo.


Tenemos una determinada API de blog que funciona de acuerdo con el siguiente principio (todas las coincidencias con API reales son un accidente):


  • una solicitud a /api/v1/index/ devolverá datos para la página principal
  • una solicitud a /api/v1/news/ devolverá datos para la página de noticias
  • una solicitud a /api/v1/articles/ devolverá datos para la lista de artículos
  • solicitar a /api/v1/article/222/ devolverá la página del artículo con id 222
  • una solicitud a /api/v1/article/edit/222/ devolverá un formulario de edición de artículo con id 222
    ... y así sucesivamente, más, más

Como puede ver, para acceder a la API, debemos recurrir a la API de una determinada versión v1 (lo poco que crecerá y se lanzará una nueva versión) y luego diseñar la solicitud de datos más.


Por lo tanto, en el código js, ​​para obtener datos, por ejemplo, un artículo con id 222, debemos escribir (para simplificar el ejemplo tanto como sea posible, usamos el método nativo js fetch):


 fetch('/api/v1/article/222/') .then(/* success */) .catch(/* error */) 

Para editar el mismo artículo, solicitaremos esto:


 fetch('/api/v1/article/edit/222/') .then(/* success */) .catch(/* error */) 

Seguramente ya ha notado que en nuestras solicitudes hay muchas rutas duplicadas. Por ejemplo, la ruta y la versión de nuestra API /api/v1/ , y trabajar con un artículo /api/v1/article/ y /api/v1/article/edit/ .


Siguiendo nuestra regla DRY (No repetir) favorita, ¿cómo optimizar el código de solicitud de API?


Podemos agregar partes de consulta a constantes, por ejemplo:


 const API = '/api' const VERSION = '/v1' const ARTICLE = `${API}${VERSION}/article` 

Y ahora podemos reescribir los ejemplos anteriores de esta manera:


Solicitud de artículo


 fetch(`${ARTICLE}/222/`) 

Solicitud de edición de artículo


 fetch(`${ARTICLE}/edit/222/`) 

El código parece ser menor, hay constantes relacionadas con la API, pero usted y yo sabemos qué se puede hacer mucho más convenientemente.


Creo que todavía hay opciones para resolver el problema, pero nuestra tarea es considerar la solución usando curry.


El principio de crear solicitudes basadas en servicios http


La estrategia es crear una determinada función, llamando a lo que construiremos solicitudes de API.


Cómo debería funcionar


Construimos la solicitud llamando a la función contenedora sobre la búsqueda nativa (llamémosla http. A continuación se muestra el código completo para esta función), en cuyos argumentos pasamos los parámetros de solicitud:


 cosnt httpToArticleId222 = http({ url: '/api/v1/article/222/', method: 'POST' }) 

Tenga en cuenta que el resultado de esta función http será una función que contenga la configuración de solicitud de método y url.


Ahora, llamando a httpToArticleId222() , realmente enviamos la solicitud a la API.


Puede hacer consultas de diseño más complicadas y por fases. Por lo tanto, podemos crear un conjunto de funciones listas para usar con rutas de API cableadas. Los llamaremos servicios http.


Entonces, primero, estamos construyendo un servicio de llamada API (agregando simultáneamente parámetros de solicitud que no se modifican para todas las solicitudes posteriores, por ejemplo, un método)


 const httpAPI = http({ url: '/api', method: 'POST' }) 

Ahora creamos el servicio de acceso a la API de la primera versión. En el futuro, podremos crear una rama de solicitud separada del servicio httpAPI a una versión diferente de la API.


 const httpAPIv1 = httpAPI({ url: '/v1' }) 

El servicio para acceder a la API de la primera versión está listo. Ahora crearemos servicios para el resto de los datos a partir de ellos (recuerde la lista improvisada al comienzo del artículo)


Datos de la página de inicio


 const httpAPIv1Main = httpAPIv1({ url: '/index' }) 

Datos de la página de noticias


 const httpAPIv1News = httpAPIv1({ url: '/news' }) 

Datos de la lista de artículos


 const httpAPIv1Articles = httpAPIv1({ url: '/articles' }) 

Finalmente llegamos a nuestro ejemplo principal, datos para el material.


 const httpAPIv1Article = httpAPIv1({ url: '/article' }) 

¿Cómo obtener el camino hacia la edición del artículo? Por supuesto, lo has adivinado, estamos cargando datos de la función previamente creada httpAPIv1Article


 const httpAPIv1ArticleEdit = httpAPIv1({ url: '/edit' }) 

Un pequeño resultado lógico


Entonces, tenemos una hermosa lista de servicios que, por ejemplo, están en un archivo separado, lo que no nos molesta en absoluto. Si hay que cambiar algo en la solicitud, sé exactamente dónde editar.


 export { httpAPIv1Main, httpAPIv1News, httpAPIv1Articles, httpAPIv1Article, httpAPIv1ArticleEdit } 

Estoy importando un servicio con una función específica


 import { httpAPIv1Article } from 'services' 

Y ejecuto la solicitud, primero la reconstruyo agregando la identificación del material, y luego llamo a la función para enviar la solicitud (como dicen: "fácil")


 httpAPIv1Article({ url: ArticleID // id  -   })() .then(/* success */) .catch(/* error */) 

Limpio, hermoso, comprensible (no publicitario)


Como funciona


Podemos "cargar" una función con datos precisamente debido al curry.


Un poco de teoría
El curry es una forma de construir una función con la capacidad de aplicar gradualmente sus argumentos. Esto se logra al devolver la función después de que se llama.


Un ejemplo clásico es la suma.
Tenemos una función de suma, la primera vez que llamamos, pasamos el primer número para el plegamiento posterior. Después de llamarlo, obtenemos una nueva función que espera un segundo número para calcular la suma. Aquí está su código (sintaxis ES6)


 const sum = a => b => a + b 

Lo llamamos la primera vez (aplicación parcial) y guardamos el resultado en una variable, por ejemplo, sum13


 const sum13 = sum(13) 

Ahora sum13 también podemos llamar con el número faltante, en el argumento, cuyo resultado será 13 + el segundo argumento


 sum13(7) // =>  20 

Bueno, ¿cómo aplicar esto a nuestra tarea?


Creamos la función http , que será el contenedor sobre la búsqueda


 function http (paramUser) {} 

donde paramUser son parámetros de solicitud pasados ​​en el momento de la llamada a la función


Comencemos agregando lógica a nuestra función.


Agregar parámetros de solicitud establecidos de forma predeterminada.


 function http (paramUser) { /** *  -,    * @type {string} */ let param = { method: 'GET', credentials: 'same-origin' } } 

Y luego, la función paramGen que genera parámetros de solicitud a partir de aquellos que están configurados por defecto y definidos por el usuario (de hecho, solo una fusión de dos objetos)


 function http (paramUser) { /** *  -,    * @type {string} */ let param = { method: 'GET', credentials: 'same-origin' } /** *   , *  url  ,          * * @param {object} param   * @param {object} paramUser ,    * * @return {object}     */ function paramGen (param, paramUser) { let url = param.url || '' let newParam = Object.assign({}, param, paramUser) url += paramUser.url || '' newParam.url = url return newParam } } 

Pasamos a lo más importante, describimos curry


La función llamada, por ejemplo, fabric y devuelta por la función http nos ayudará en esto.


 function http (paramUser) { /** *  -,    * @type {string} */ let param = { method: 'GET', credentials: 'same-origin' } /** *   , *  url  ,          * * @param {object} param   * @param {object} paramUser ,    * * @return {object}     */ function paramGen (param, paramUser) { let url = param.url || '' url += paramUser.url || '' let newParam = Object.assign({}, param, paramUser); newParam.url = url return newParam } /** *  ,     *  ,      * *  : * * -    ,        ,     * -   ,           * -   ,      * * @param {object} param ,       * @param {object} paramUser ,   * * @return {function || promise}   ,    (fetch),     */ function fabric (param, paramUser) { if (paramUser) { if (typeof paramUser === 'string') { return fabric.bind(null, paramGen(param, { url: paramUser })) } return fabric.bind(null, paramGen(param, paramUser)) } else { //  ,   ,   param    url, //       :) return fetch(param.url, param) } } return fabric.bind(null, paramGen(param, paramUser)) } 

La primera llamada a la función http devuelve la función fabric , con parámetros param pasados ​​(y configurados por la función paramGen ), que esperará su horas llama mas tarde.


Por ejemplo, configure la solicitud


 let httpGift = http({ url: '//omozon.ru/givemegift/' }) 

Y llamando a httpGift , se aplican los parámetros pasados, como resultado, devolvemos fetch , si queremos reconfigurar la solicitud, simplemente pasamos los nuevos parámetros a la función httpGift generada y esperamos que se invoque sin argumentos


 httpGift() .then(/* success */) .catch(/* error */) 

Resumen


Gracias al uso de curry en el desarrollo de varios módulos, podemos lograr una gran flexibilidad en el uso de módulos y la facilidad de prueba. Como, por ejemplo, al organizar la arquitectura de servicios para trabajar con la API.


Es como si estuviéramos creando una mini biblioteca, usando las herramientas de las cuales estamos creando una infraestructura única para nuestra aplicación.


Espero que la información haya sido útil, no la golpeen, este es mi primer artículo en mi vida :)


Todo el código compilado, ¡hasta pronto!

Source: https://habr.com/ru/post/es422661/


All Articles