Olá pessoal! Não é segredo para ninguém que no mundo da programação existem muitas técnicas, práticas e padrões de programação (design), mas, muitas vezes, aprendendo algo novo, não está totalmente claro onde e como aplicar esse novo.
Hoje, usando o exemplo da criação de um pequeno módulo wrapper para trabalhar com solicitações http, analisaremos os reais benefícios do curry - a recepção da programação funcional.
A todos os recém-chegados e interessados em usar a programação funcional na prática - bem-vindos, aqueles que entendem perfeitamente o que é o curry - aguardo seus comentários sobre o código, porque, como eles dizem - não há limite para a perfeição.
Então vamos começar
Mas não a partir do conceito de curry, mas a partir da declaração do problema, onde podemos aplicá-lo.
Temos uma certa API de blog que trabalha de acordo com o seguinte princípio (todas as correspondências com APIs reais são um acidente):
- uma solicitação para
/api/v1/index/
retornará dados para a página principal - uma solicitação para
/api/v1/news/
retornará dados para a página de notícias - uma solicitação para
/api/v1/articles/
retornará dados para a lista de artigos - solicitação para
/api/v1/article/222/
retornará a página do artigo com o ID 222 - uma solicitação para
/api/v1/article/edit/222/
retornará um formulário de edição de artigo com o ID 222
... e assim por diante
Como você pode ver, para acessar a API, precisamos recorrer à API de uma determinada versão v1 (quão pouco ela crescerá e uma nova versão será lançada) e depois projetar ainda mais a solicitação de dados.
Portanto, no código js, para obter dados, por exemplo, um artigo com o ID 222, precisamos escrever (para simplificar o exemplo o máximo possível, usamos o método js fetch nativo):
fetch('/api/v1/article/222/') .then() .catch()
Para editar o mesmo artigo, solicitaremos o seguinte:
fetch('/api/v1/article/edit/222/') .then() .catch()
Certamente você já percebeu que em nossos pedidos há muitos caminhos duplicados. Por exemplo, o caminho e a versão da nossa API /api/v1/
e trabalhando com um artigo /api/v1/article/
e /api/v1/article/edit/
.
Seguindo nossa regra DRY favorita (não se repita), como otimizar o código de solicitação da API?
Podemos adicionar partes de consulta a constantes, por exemplo:
const API = '/api' const VERSION = '/v1' const ARTICLE = `${API}${VERSION}/article`
E agora podemos reescrever os exemplos acima desta maneira:
Pedido de Artigo
fetch(`${ARTICLE}/222/`)
Solicitação de edição de artigo
fetch(`${ARTICLE}/edit/222/`)
O código parece ser menor, há constantes relacionadas à API, mas você e eu sabemos o que pode ser feito de maneira muito mais conveniente.
Acredito que ainda existem opções para resolver o problema, mas nossa tarefa é considerar a solução usando o curry.
O princípio de criar solicitações com base em serviços http
A estratégia é criar uma determinada função, chamando a qual iremos construir solicitações de API.
Como deve funcionar
Construímos a solicitação chamando a função wrapper sobre a busca nativa (vamos chamá-la http. Abaixo está o código completo para essa função), nos argumentos dos quais passamos os parâmetros da solicitação:
cosnt httpToArticleId222 = http({ url: '/api/v1/article/222/', method: 'POST' })
Observe que o resultado dessa função http será uma função que contém as configurações de solicitação de URL e método.
Agora, chamando httpToArticleId222()
, na verdade, enviamos a solicitação para a API.
Você pode fazer consultas de design mais complicadas e em fases. Assim, podemos criar um conjunto de funções prontas com caminhos de API com fio. Nós os chamaremos de serviços http.
Então, primeiro, estamos construindo um serviço de chamada de API (adicionando simultaneamente parâmetros de solicitação inalterados para todas as solicitações subsequentes, por exemplo, um método)
const httpAPI = http({ url: '/api', method: 'POST' })
Agora criamos o serviço de acesso à API da primeira versão. No futuro, poderemos criar uma ramificação de solicitação separada do serviço httpAPI para uma versão diferente da API.
const httpAPIv1 = httpAPI({ url: '/v1' })
O serviço para acessar a API da primeira versão está pronto. Agora, criaremos serviços para o restante dos dados (lembre-se da lista improvisada no início do artigo)
Dados da página inicial
const httpAPIv1Main = httpAPIv1({ url: '/index' })
Dados da página de notícias
const httpAPIv1News = httpAPIv1({ url: '/news' })
Dados da lista de artigos
const httpAPIv1Articles = httpAPIv1({ url: '/articles' })
Finalmente, chegamos ao nosso exemplo principal, dados para o material
const httpAPIv1Article = httpAPIv1({ url: '/article' })
Como obter o caminho para a edição de artigos? Obviamente, você adivinhou, estamos carregando dados da função criada anteriormente httpAPIv1
const httpAPIv1ArticleEdit = httpAPIv1({ url: '/edit' })
Um pequeno resultado lógico
Portanto, temos uma bela lista de serviços que, por exemplo, estão em algum arquivo separado, o que não nos incomoda. Se algo precisar ser alterado na solicitação, eu sei exatamente onde editar.
export { httpAPIv1Main, httpAPIv1News, httpAPIv1Articles, httpAPIv1Article, httpAPIv1ArticleEdit }
Estou importando um serviço com uma função específica
import { httpAPIv1Article } from 'services'
E eu executo a solicitação, reconstruindo-a primeiro adicionando o ID do material e depois chamo a função para enviar a solicitação (como eles dizem: "fácil")
httpAPIv1Article({ url: ArticleID // id - })() .then() .catch()
Limpo, bonito, compreensível (sem publicidade)
Como isso funciona
Podemos "carregar" uma função com dados precisamente devido ao currying.
Um pouco de teoria.
O curry é uma maneira de construir uma função com a capacidade de aplicar gradualmente seus argumentos. Isso é alcançado retornando a função após ser chamada.
Um exemplo clássico é a adição.
Temos uma função de soma, na primeira vez que chamamos, passamos o primeiro número para a dobragem subsequente. Após chamá-lo, obtemos uma nova função que espera um segundo número para calcular a soma. Aqui está o código dela (sintaxe ES6)
const sum = a => b => a + b
Chamamos isso pela primeira vez (aplicação parcial) e salvamos o resultado em uma variável, por exemplo, sum13
const sum13 = sum(13)
Agora sum13 também podemos chamar com o número ausente, no argumento, cujo resultado será 13 + o segundo argumento
sum13(7)
Bem, como aplicar isso à nossa tarefa?
Criamos a função http , que será o wrapper durante a busca
function http (paramUser) {}
em que paramUser são parâmetros de solicitação passados no momento da chamada da função
Vamos começar a adicionar lógica à nossa função.
Adicione parâmetros de solicitação definidos por padrão.
function http (paramUser) { /** * -, * @type {string} */ let param = { method: 'GET', credentials: 'same-origin' } }
E então a função paramGen que gera parâmetros de solicitação daqueles que são definidos por padrão e definidos pelo usuário (na verdade, apenas uma mesclagem de dois 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 } }
Passamos para o mais importante, descrevemos o curry
A função chamada, por exemplo, fabric e retornada pela função http nos ajudará nisso.
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)) }
A primeira chamada para a função http retorna a função fabric , com os parâmetros param passados (e configurados pela função paramGen ), que aguardará sua horas ligue mais tarde.
Por exemplo, configure a solicitação
let httpGift = http({ url: '
E chamando httpGift , os parâmetros passados são aplicados; como resultado, retornamos a busca . Se queremos reconfigurar a solicitação, simplesmente passamos os novos parâmetros para a função httpGift gerada e esperamos que ela seja chamada sem argumentos
httpGift() .then() .catch()
Sumário
Graças ao uso de currying no desenvolvimento de vários módulos, podemos obter alta flexibilidade no uso de módulos e facilidade de teste. Como, por exemplo, ao organizar a arquitetura de serviços para trabalhar com a API.
É como se estivéssemos criando uma mini-biblioteca, usando as ferramentas das quais estamos criando uma única infraestrutura para nosso aplicativo.
Espero que a informação tenha sido útil, não bata forte, este é o meu primeiro artigo na minha vida :)
Todo o código compilado, até breve!