Bonjour à tous! Ce n'est un secret pour personne que dans le monde de la programmation, il existe de nombreuses techniques, pratiques et modèles de programmation (conception), mais souvent, en apprenant quelque chose de nouveau, il n'est pas du tout clair où et comment appliquer cette nouvelle.
Aujourd'hui, en utilisant l'exemple de la création d'un petit module wrapper pour travailler avec les requêtes http, nous analyserons les avantages réels du curry - la réception de la programmation fonctionnelle.
À tous les nouveaux arrivants et à ceux qui souhaitent utiliser la programmation fonctionnelle dans la pratique - bienvenue, ceux qui comprennent parfaitement ce qu'est le curry - j'attends avec impatience vos commentaires sur le code, car comme ils disent - il n'y a pas de limite à la perfection.
Commençons donc
Mais pas du concept de curry, mais de l'énoncé du problème, où nous pouvons l'appliquer.
Nous avons une certaine API de blog fonctionnant selon le principe suivant (toutes les correspondances avec de vraies API sont un accident):
- une demande à
/api/v1/index/
renverra des données pour la page principale - une demande à
/api/v1/news/
renverra des données pour la page d'actualités - une demande à
/api/v1/articles/
renverra des données pour la liste des articles - demander à
/api/v1/article/222/
renverra la page de l'article avec l'ID 222 - une demande à
/api/v1/article/edit/222/
renverra un formulaire d'édition d'article avec l'identifiant 222
... et ainsi de suite, plus loin
Comme vous pouvez le voir, pour accéder à l'API, nous devons utiliser l' API d'une certaine version de v1 (le peu de choses qu'il va croître et une nouvelle version sera publiée), puis concevoir la demande de données plus loin.
Par conséquent, dans le code js, pour obtenir des données, par exemple, un article avec l' ID 222, nous devons écrire (pour simplifier autant que possible l'exemple, utilisez la méthode native js fetch):
fetch('/api/v1/article/222/') .then() .catch()
Pour modifier le même article, nous vous demanderons ceci:
fetch('/api/v1/article/edit/222/') .then() .catch()
Vous avez sûrement déjà remarqué que dans nos demandes il y a beaucoup de chemins en double. Par exemple, le chemin et la version de notre API /api/v1/
, et travailler avec un article /api/v1/article/
et /api/v1/article/edit/
.
En suivant notre règle DRY préférée (ne vous répétez pas), comment optimiser le code de demande d'API?
Nous pouvons ajouter des parties de requête aux constantes, par exemple:
const API = '/api' const VERSION = '/v1' const ARTICLE = `${API}${VERSION}/article`
Et maintenant, nous pouvons réécrire les exemples ci-dessus de cette manière:
Demande d'article
fetch(`${ARTICLE}/222/`)
Demande de modification d'article
fetch(`${ARTICLE}/edit/222/`)
Le code semble être moins, il y a des constantes liées à l'API, mais vous et moi savons ce qui peut être fait beaucoup plus facilement.
Je crois qu'il existe encore des options pour résoudre le problème, mais notre tâche est d'envisager la solution en utilisant le curry.
Le principe de construction des requêtes à partir des services http
La stratégie consiste à créer une certaine fonction, en appelant laquelle nous allons construire des requêtes API.
Comment ça devrait fonctionner
Nous construisons la requête en appelant la fonction wrapper sur la récupération native (appelons-la http. Ci-dessous le code complet de cette fonction), dans les arguments dont nous passons les paramètres de la requête:
cosnt httpToArticleId222 = http({ url: '/api/v1/article/222/', method: 'POST' })
Veuillez noter que le résultat de cette fonction http sera une fonction qui contient les paramètres de demande d'url et de méthode.
Maintenant, en appelant httpToArticleId222()
nous envoyons réellement la demande à l'API.
Vous pouvez effectuer des requêtes de conception plus délicates et échelonnées. Ainsi, nous pouvons créer un ensemble de fonctions prêtes à l'emploi avec des chemins d'API câblés. Nous les appellerons services http.
Donc, tout d'abord, nous construisons un service d'appel API (en ajoutant simultanément des paramètres de demande inchangés pour toutes les demandes suivantes, par exemple, une méthode)
const httpAPI = http({ url: '/api', method: 'POST' })
Nous créons maintenant le service d'accès à l'API de la toute première version. À l'avenir, nous pourrons créer une branche de demande distincte du service httpAPI vers une version différente de l'API.
const httpAPIv1 = httpAPI({ url: '/v1' })
Le service d'accès à la première version de l'API est prêt. Nous allons maintenant créer des services pour le reste des données (rappelez-vous la liste improvisée au début de l'article)
Données de la page d'accueil
const httpAPIv1Main = httpAPIv1({ url: '/index' })
Données de la page d'actualités
const httpAPIv1News = httpAPIv1({ url: '/news' })
Données de la liste d'articles
const httpAPIv1Articles = httpAPIv1({ url: '/articles' })
Enfin, nous arrivons à notre exemple principal, les données pour le matériau
const httpAPIv1Article = httpAPIv1({ url: '/article' })
Comment obtenir le chemin de l'édition d'un article? Bien sûr, vous l'avez deviné, nous chargeons les données de la fonction précédemment créée httpAPIv1Article
const httpAPIv1ArticleEdit = httpAPIv1({ url: '/edit' })
Un petit résultat logique
Nous avons donc une belle liste de services qui, par exemple, sont dans un fichier séparé, ce qui ne nous dérange pas du tout. Si quelque chose doit être changé dans la demande, je sais exactement où modifier.
export { httpAPIv1Main, httpAPIv1News, httpAPIv1Articles, httpAPIv1Article, httpAPIv1ArticleEdit }
J'importe un service avec une fonction spécifique
import { httpAPIv1Article } from 'services'
Et j'exécute la demande, la reconstruisant d'abord en ajoutant l'id du matériel, puis j'appelle la fonction pour envoyer la demande (comme on dit: "facile")
httpAPIv1Article({ url: ArticleID // id - })() .then() .catch()
Propre, beau, compréhensible (pas de publicité)
Comment ça marche
Nous pouvons «charger» une fonction avec des données précisément en raison du curry.
Un peu de théorie.
Le curry est une façon de construire une fonction avec la possibilité d'appliquer progressivement ses arguments. Ceci est réalisé en renvoyant la fonction après son appel.
Un exemple classique est l'addition.
Nous avons une fonction somme, la première fois que nous appelons, nous passons le premier nombre pour le pliage ultérieur. Après l'avoir appelée, nous obtenons une nouvelle fonction qui attend un deuxième nombre pour calculer la somme. Voici son code (syntaxe ES6)
const sum = a => b => a + b
Nous l'appelons la première fois (application partielle) et enregistrons le résultat dans une variable, par exemple, sum13
const sum13 = sum(13)
Maintenant, sum13 nous pouvons également appeler avec le nombre manquant, dans l'argument, dont le résultat sera 13 + le deuxième argument
sum13(7)
Eh bien, comment appliquer cela à notre tâche?
Nous créons la fonction http , qui sera le wrapper over fetch
function http (paramUser) {}
où paramUser sont des paramètres de requête passés au moment de l'appel de fonction
Commençons à ajouter de la logique à notre fonction.
Ajoutez les paramètres de requête définis par défaut.
function http (paramUser) { /** * -, * @type {string} */ let param = { method: 'GET', credentials: 'same-origin' } }
Et puis la fonction paramGen , qui génère des paramètres de requête à partir de ceux qui sont définis par défaut et définis par l'utilisateur (en fait, juste une fusion de deux objets)
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 } }
On passe au plus important, on décrit le curry
La fonction appelée, par exemple, fabric et renvoyée par la fonction http nous y aidera.
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)) }
Le premier appel à la fonction http renvoie la fonction fabric , avec des paramètres param qui lui sont passés (et configurés par la fonction paramGen ), qui attendra sa heures appeler plus tard.
Par exemple, configurez la demande
let httpGift = http({ url: '
Et en appelant httpGift , les paramètres passés sont appliqués, par conséquent, nous retournons chercher , si nous voulons reconfigurer la demande, nous passons simplement les nouveaux paramètres à la fonction httpGift générée et nous attendons à ce qu'elle soit appelée sans arguments
httpGift() .then() .catch()
Résumé
Grâce à l'utilisation du curry dans le développement de divers modules, nous pouvons atteindre une grande flexibilité dans l'utilisation des modules et une facilité de test. Comme, par exemple, lors de l'organisation de l'architecture des services pour travailler avec l'API.
C'est comme si nous créons une mini-bibliothèque, en utilisant les outils dont nous créons une infrastructure unique pour notre application.
J'espère que les informations vous ont été utiles, ne frappez pas fort, c'est mon premier article de ma vie :)
Tout le code compilé, à bientôt!