大家好! 在编程世界中,有许多技术,实践和编程模式(设计)对任何人来说都是秘密,但是,经常学习新知识,却不清楚在何处以及如何应用新知识。
今天,以创建一个小型包装器模块处理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() .catch()
要编辑同一篇文章,我们将要求您这样做:
fetch('/api/v1/article/edit/222/') .then() .catch()
当然,您已经注意到,在我们的请求中,有很多重复的路径。 例如,我们的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() .catch()
干净,美观,易于理解(不做广告)
如何运作
正是由于curring,我们才能用数据“加载”函数。
有点理论。
Currying是一种构造函数的方法,可以逐步应用其参数。 这是通过在调用函数后返回它来实现的。
一个典型的例子是加法。
我们有一个sum函数,这是我们第一次调用时,我们传递第一个数字以进行后续折叠。 调用它之后,我们得到一个新函数,该函数期望第二个数字来计算总和。 这是她的代码(ES6语法)
const sum = a => b => a + b
我们第一次调用它(部分应用程序)并将结果保存在变量中,例如sum13
const sum13 = sum(13)
现在sum13我们还可以在参数中使用缺少的数字进行调用,其结果将是13 +第二个参数
sum13(7)
那么,如何将其应用于我们的任务?
我们创建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: '
并调用httpGift ,应用传递的参数,结果返回fetch ,如果要重新配置请求,我们只需将新参数传递给生成的httpGift函数,并期望不带参数地调用它
httpGift() .then() .catch()
总结
通过在各种模块的开发中使用curring,我们可以在模块使用和测试简便方面获得高度的灵活性。 例如,当组织服务架构以使用API时。
就像我们正在创建一个小型库一样,使用我们的工具为应用程序创建单个基础结构。
我希望这些信息有用,不要再费力,这是我一生中的第一篇文章:)
所有已编译的代码,一会见!