使用http请求模块的示例在js中实际使用currying

大家好! 在编程世界中,有许多技术,实践和编程模式(设计)对任何人来说都是秘密,但是,经常学习新知识,却不清楚在何处以及如何应用新知识。


今天,以创建一个小型包装器模块处理http请求的示例为例,我们将分析currying的真正好处-函数式编程的接收。


对于所有新手和有兴趣在实践中使用函数式编程的人-欢迎,那些完全理解什么是currying的人-我期待您对代码的评论,因为正如他们所说-完美无止境。


所以我们开始吧


但是,不是从“ curry”的概念出发,而是从问题的陈述出发,我们可以在哪里应用它。


我们有一个博客API会根据以下原则工作(与真实API的所有匹配都是偶然的):


  • /api/v1/index/的请求将返回主页的数据
  • /api/v1/news/的请求将返回新闻页面的数据
  • /api/v1/articles/的请求将返回文章列表的数据
  • 请求/api/v1/article/222/将返回ID为222的文章页面
  • /api/v1/article/edit/222/的请求将返回ID为222的文章编辑表单
    ...等等,更进一步

如您所见,为了访问API,我们需要转到特定版本v1 (它将增长多少并发布新版本)的api,然后进一步设计数据请求。


因此,在js代码中,要获取数据,例如,一篇ID为222的文章我们必须编写(为简化示例,我们使用了本机js fetch方法):


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

要编辑同一篇文章,我们将要求您这样做:


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

当然,您已经注意到,在我们的请求中,有很多重复的路径。 例如,我们的API /api/v1/的路径和版本,并使用一个article /api/v1/article//api/v1/article/edit/


遵循我们最喜欢的DRY(不要自己重复)规则,如何优化API请求代码?


我们可以将查询部分添加到常量中,例如:


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

现在,我们可以通过以下方式重写上面的示例:


文章要求


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

文章编辑要求


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

代码似乎更少了,有一些与API相关的常量,但是您和我都知道可以更方便地完成操作。


我相信仍有解决问题的选项,但我们的任务是使用curring考虑解决方案。


基于http服务构建请求的原则


策略是通过调用我们将构造API请求的方法来创建某个函数。


它应该如何工作


我们通过在本机访存上调用包装函数(我们将其称为http。下面是该函数的完整代码)来构造请求,在该参数中,我们传递请求参数:


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

请注意,此http函数的结果将是包含url和方法请求设置的函数。


现在,通过调用httpToArticleId222()我们实际上将请求发送到了API。


您可以进行棘手的分阶段设计查询。 因此,我们可以使用有线API路径创建一组现成的函数。 我们将它们称为http服务。


因此,首先,我们正在构建一个API调用服务(同时添加对于所有后续请求都不变的请求参数,例如,方法)


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

现在,我们创建访问第一个版本的API的服务。 将来,我们将能够创建一个从httpAPI服务到不同版本API的独立请求分支。


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

用于访问第一版API的服务已准备就绪。 现在,我们将为其中的其余数据创建服务(请记住本文开头的临时列表)


主页数据


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

新闻页面数据


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

文章列表数据


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

最后,我们来看我们的主要示例, 材料的数据


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

如何获得文章编辑的路径? 当然,您猜对了,我们正在从先前创建的函数httpAPIv1Article中加载数据


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

一个小的逻辑结果


因此,我们有一个很漂亮的服务列表,例如,这些服务位于单独的文件中,根本不会打扰我们。 如果需要在请求中进行某些更改,我确切地知道在哪里进行编辑。


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

我正在导入具有特定功能的服务


 import { httpAPIv1Article } from 'services' 

然后执行请求,首先通过添加材料的ID来重建请求,然后调用该函数发送请求(正如他们所说的:“简单”)


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

干净,美观,易于理解(不做广告)


如何运作


正是由于curring,我们才能用数据“加载”函数。


有点理论。
Currying是一种构造函数的方法,可以逐步应用其参数。 这是通过在调用函数后返回它来实现的。


一个典型的例子是加法。
我们有一个sum函数,这是我们第一次调用时,我们传递第一个数字以进行后续折叠。 调用它之后,我们得到一个新函数,该函数期望第二个数字来计算总和。 这是她的代码(ES6语法)


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

我们第一次调用它(部分应用程序)并将结果保存在变量中,例如sum13


 const sum13 = sum(13) 

现在sum13我们还可以在参数中使用缺少的数字进行调用,其结果将是13 +第二个参数


 sum13(7) // =>  20 

那么,如何将其应用于我们的任务?


我们创建http函数,这将是fetch的包装器


 function http (paramUser) {} 

其中paramUser是函数调用时传递的请求参数


让我们开始向函数添加逻辑。


添加默认设置的请求参数。


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

然后是paramGen函数,该函数根据默认设置和用户定义的参数生成请求参数(实际上,这是两个对象的合并)


 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 } } 

我们传递到最重要的地方,我们描述了柯里化


例如,由http函数返回的称为fabric的函数将对此提供帮助。


 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)) } 

首次调用http函数将返回fabric函数,并向其传递param参数(并由paramGen函数配置),它将等待其返回 小时 稍后再打电话。


例如,配置请求


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

并调用httpGift ,应用传递的参数,结果返回fetch ,如果要重新配置请求,我们只需将新参数传递给生成的httpGift函数,并期望不带参数地调用它


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

总结


通过在各种模块的开发中使用curring,我们可以在模块使用和测试简便方面获得高度的灵活性。 例如,当组织服务架构以使用API​​时。


就像我们正在创建一个小型库一样,使用我们的工具为应用程序创建单个基础结构。


我希望这些信息有用,不要再费力,这是我一生中的第一篇文章:)


所有已编译的代码,一会见!

Source: https://habr.com/ru/post/zh-CN422661/


All Articles