Validation par promesse

Le concept de "promesses" a changé la façon dont nous écrivons du JavaScript asynchrone. Au cours de l'année écoulée, de nombreux frameworks ont incorporé une forme du motif Promise pour faciliter l'écriture, la lecture et la maintenance du code asynchrone. Par exemple, jQuery a ajouté $ .Deferred () et NodeJS possède les modules Q et jspromise qui fonctionnent à la fois sur le client et sur le serveur. Les frameworks MVC côté client, tels que EmberJS et AngularJS, implémentent également leurs propres versions de Promises.

Mais cela ne doit pas s'arrêter là: nous pouvons repenser les solutions plus anciennes et leur appliquer des promesses. Dans cet article, nous allons faire exactement cela: valider un formulaire en utilisant le modèle Promise pour exposer une API super simple.


Qu'est-ce qu'une promesse?

Les promesses notifient le résultat d'une opération.

En termes simples, Promises notifie le résultat d’une opération. Le résultat peut être un succès ou un échec, et l'opération elle-même peut être tout ce qui respecte un simple contrat. J'ai choisi d'utiliser le mot Contrat parce que vous pouvez concevoir ce contrat de différentes manières. Heureusement, la communauté de développement est parvenue à un consensus et a créé une spécification appelée Promises / A+.

Seule l'opération sait vraiment quand elle est terminée. en tant que tel, il est responsable de la notification de son résultat à l'aide du contrat Promises / A +. En d'autres termes, il promesses pour vous dire le résultat final à l'achèvement.

L'opération retourne un promettre objet, et vous pouvez y attacher vos rappels en utilisant le terminé() ou échouer() méthodes. L’opération peut notifier son résultat en appelant le promise.resolve () ou promise.reject (), respectivement. Ceci est décrit dans la figure suivante:


Utilisation de promesses pour la validation de formulaire

Me laisser peindre un scénario plausible.

Nous pouvons repenser les anciennes solutions et leur appliquer des promesses.

La validation de formulaire côté client commence toujours par la plus simple des intentions. Vous pouvez avoir un formulaire d'inscription avec prénom et Email champs, et vous devez vous assurer que l'utilisateur fournit une entrée valide pour les deux champs. Cela semble assez simple, et vous commencez à mettre en œuvre votre solution.

Vous êtes ensuite informé que les adresses électroniques doivent être uniques et vous décidez de les valider sur le serveur. Ainsi, l'utilisateur clique sur le bouton d'envoi, le serveur vérifie l'unicité de l'e-mail et la page s'actualise pour afficher les erreurs éventuelles. Cela semble être la bonne approche, non? Nan. Votre client veut une expérience utilisateur lisse; les visiteurs doivent voir les messages d'erreur sans actualiser la page.

Votre formulaire a le prénom domaine qui ne nécessite aucun support côté serveur, mais alors vous avez le Email champ qui vous oblige à faire une demande au serveur. Demande de serveur signifie $ .ajax () appels, vous devrez donc valider votre courrier électronique dans votre fonction de rappel. Si votre formulaire comporte plusieurs champs nécessitant une prise en charge côté serveur, votre code sera un désordre imbriqué. $ .ajax () appels dans les rappels. Rappels à l'intérieur des rappels: "Bienvenue dans l'enfer du rappel! Nous espérons que votre séjour sera misérable!".

Alors, comment pouvons-nous gérer l'enfer de rappel?

La solution que j'ai promise

Prenez du recul et réfléchissez à ce problème. Nous avons un ensemble d'opérations qui peuvent réussir ou échouer. Chacun de ces résultats peut être saisi en tant que Promettre, et les opérations peuvent aller de simples vérifications côté client à des validations complexes côté serveur. Les promesses vous apportent également un avantage supplémentaire en termes de cohérence, tout en vous évitant une vérification conditionnelle du type de validation. Voyons comment nous pouvons faire cela.

Comme je l'ai indiqué précédemment, plusieurs implémentations de promesses sont dans la nature, mais je vais me concentrer sur l'implémentation de $ .Deferred () de jQuery.

Nous allons construire un cadre de validation simple dans lequel chaque contrôle renvoie immédiatement un résultat ou une promesse. En tant qu'utilisateur de ce framework, vous ne devez vous souvenir que d'une chose: "il retourne toujours une promesse". Commençons.

Validator Framework utilisant Promises

Je pense qu'il est plus facile d'apprécier la simplicité de Promises du point de vue du consommateur. Disons que j'ai un formulaire avec trois champs: Nom, Email et Adresse:

 

Je vais d'abord configurer les critères de validation avec l'objet suivant. Cela sert également d'API de notre framework:

 var validationConfig = '.name': vérifie: 'requis', champ: 'Nom', '.email': vérifie: ['requis'], champ: 'Email', '.adresse':  vérifie: ['aléatoire', 'requis'], champ: 'Adresse';

Les clés de cet objet config sont des sélecteurs jQuery; leurs valeurs sont des objets avec les deux propriétés suivantes:

  • chèques: une chaîne ou un tableau de validations.
  • champ: le nom de champ lisible par l'homme, qui sera utilisé pour signaler les erreurs concernant ce champ

Nous pouvons appeler notre validateur, exposé comme variable globale V, comme ça:

 V.validate (validationConfig) .done (function () // Success) .fail (function (errors) // Les validations ont échoué. Errors a les détails);

Notez l'utilisation de la terminé() et échouer() rappels; Ce sont les rappels par défaut pour la gestion du résultat d'une promesse. Si nous ajoutons plusieurs champs de formulaire, vous pouvez simplement augmenter la validationConfig objet sans perturber le reste de la configuration (le principe Ouvert-Fermé en action). En fait, nous pouvons ajouter d'autres validations, comme la contrainte d'unicité pour les adresses électroniques, en étendant le cadre de validation (que nous verrons plus tard)..

Il s’agit donc de l’API grand public du framework de validation. Maintenant, plongeons dedans et voyons comment cela fonctionne sous le capot.

Validateur, sous le capot

Le validateur est exposé en tant qu'objet avec deux propriétés:

  • type: contient les différents types de validations et sert également de point d’extension pour l’ajout de nouvelles.
  • valider: la méthode de base qui effectue les validations en fonction de l'objet config fourni.

La structure générale peut être résumée comme suit:

 var V = (fonction ($) var validateur = / * * point d'extension - ajoutez simplement à ce hachage * * V.type ['mon-validateur']] = * ok: fonction (valeur) return true; , * message: 'Message d'échec pour mon-validateur' * * / type: 'requis': ok: fonction (valeur) // est valide?, message: 'Ce champ est obligatoire',… , / ** * * @param config * * '': chaîne | objet | [string] * * / validate: function (config) // 1. Normalisez l'objet de configuration // 2. Convertissez chaque validation en une promesse // 3. Enroulez dans une promesse principale // 4. Renvoyez la promesse principale ; ) (jQuery);

le valider La méthode fournit les bases de ce cadre. Comme indiqué dans les commentaires ci-dessus, il y a quatre étapes à suivre ici:

1. Normaliser l'objet de configuration.

C'est ici que nous passons à travers notre objet config et le convertissons en une représentation interne. Il s’agit principalement de capturer toutes les informations nécessaires à la validation et de signaler les erreurs si nécessaire:

 fonction normalizeConfig (config) config = config || ; validations var = []; $ .each (config, fonction (selector, obj) // crée un tableau pour une vérification simplifiée var checks = $ .isArray (obj.checks)? obj.checks: [obj.checks]; $ .each (checks, fonction (idx, check) validations.push (control: $ (sélecteur), check: getValidator (check), checkName: check, champ: obj.field););); renvoyer les validations;  function getValidator (type) if ($ .type (type) === 'chaîne' && validator.type [type]) retour validator.type [type]; renvoyer validator.noCheck; 

Ce code parcourt les clés de l'objet config et crée une représentation interne de la validation. Nous allons utiliser cette représentation dans le valider méthode.

le getValidator () helper récupère l'objet validateur à partir du type hacher. Si on n'en trouve pas, on retourne le noCheck validateur qui retourne toujours vrai.

2. Convertir chaque validation en une promesse.

Ici, nous nous assurons que chaque validation est une promesse en vérifiant la valeur de retour de validation.ok (). Si elle contient le puis() méthode, nous savons que c’est une promesse (c’est selon les spécifications Promises / A +). Sinon, nous créons une promesse ad-hoc qui résout ou rejette en fonction de la valeur de retour.

 validate: function (config) // 1. Normalisez l'objet de configuration config = normalizeConfig (config); var promises = [], vérifie = []; // 2. Convertit chaque validation en une promesse $ .each (config, function (idx, v) var value = v.control.val (); var retVal = v.check.ok (value); // Crée un promise, check est basé sur Promises / A + spec if (retVal.then) promises.push (retVal); else var p = $ .Deferred (); if (retVal) p.resolve (); sinon p.reject (); promises.push (p.promise ()); checks.push (v);); // 3. Enroulez dans une promesse principale // 4. Renvoyez la promesse principale

3. Envelopper dans une promesse de maître.

Nous avons créé un tableau de promesses à l'étape précédente. Quand ils réussissent tous, nous voulons soit résoudre une fois ou échouer avec des informations d'erreur détaillées. Nous pouvons le faire en regroupant toutes les promesses dans une seule promesse et en propageant le résultat. Si tout se passe bien, nous nous contenterons de la promesse principale.

En cas d'erreur, nous pouvons lire notre déclaration de validation interne et l'utiliser pour la génération de rapports. Puisqu'il peut y avoir plusieurs échecs de validation, nous passons en boucle sur promesses tableau et lire le Etat() résultat. Nous rassemblons toutes les promesses rejetées dans la échoué tableau et appel rejeter() sur la promesse principale:

 // 3. Emballer dans une promesse principale var masterPromise = $ .Deferred (); $ .when.apply (null, promises) .done (function () masterPromise.resolve ();) .fail (function () var échoué = []; $ .each (promesses, fonction (idx, x) if (x.state () === 'rejeté') var failedCheck = vérifie [idx]; var erreur = check: failedCheck.checkName, erreur: failedCheck.check.message, champ: failedCheck.field, contrôle: failedCheck.control; failed.push (error);); masterPromise.reject (failed);); // 4. Renvoie la promesse principale return masterPromise.promise ();

4. Retourner la promesse principale.

Enfin, nous retournons la promesse principale du valider() méthode. C’est la promesse sur laquelle le code client configure la terminé() et échouer() rappels.

Les étapes deux et trois constituent le noeud de ce cadre. En normalisant les validations dans une promesse, nous pouvons les traiter de manière cohérente. Nous avons plus de contrôle avec un objet Promise principal et nous pouvons associer des informations contextuelles supplémentaires pouvant être utiles à l'utilisateur final..


Utiliser le validateur

Voir le fichier de démonstration pour une utilisation complète du framework de validation. Nous utilisons le terminé() rappel pour signaler le succès et échouer() pour afficher une liste des erreurs dans chacun des champs. Les captures d'écran ci-dessous montrent les états de succès et d'échec:

La démo utilise le même code HTML et la même configuration de validation que ceux mentionnés précédemment dans cet article. Le seul ajout est le code qui affiche les alertes. Notez l'utilisation de la terminé() et échouer() callbacks pour gérer les résultats de validation.

 function showAlerts (errors) var alertContainer = $ ('. alert'); $ ('. erreur'). remove (); if (! errors) alertContainer.html ('Tous passé');  else $ .each (erreurs, fonction (idx, erreur) var msg = $ ('') .addClass (' error ') .text (err.error); err.control.parent (). append (msg); );  $ ('. validate'). click (function () $ ('. indicateur'). show (); $ ('. alert'). empty (); V.validate (validationConfig) .done (fonction () $ ('. indicateur'). masquer (); showAlerts ();) .fail (fonction (erreurs) $ ('. indicateur'). masquer (); showAlerts (erreurs);); )

Étendre le validateur

J'ai mentionné précédemment que nous pouvons ajouter plus d'opérations de validation au framework en étendant le validateur. type hacher. Prendre en compte au hasard validateur à titre d'exemple. Ce validateur réussit ou échoue de manière aléatoire. Je sais que ce n'est pas un validateur utile, mais il convient de noter certains de ses concepts:

  • Utilisation setTimeout () rendre la validation async. Vous pouvez également considérer cela comme une simulation de la latence du réseau..
  • Retourner une promesse du D'accord() méthode.
 // Extension avec un validateur aléatoire V.type ['random'] = ok: fonction (valeur) var deferred = $ .Deferred (); setTimeout (function () var result = Math.random () < 0.5; if (result) deferred.resolve(); else deferred.reject(); , 1000); return deferred.promise(); , message: 'Failed randomly. No hard feelings.' ;

Dans la démo, j’ai utilisé cette validation sur le Adresse champ comme si:

 var validationConfig = / * pour plus de brièveté * / '.Adresse': vérifie: ['aléatoire', 'obligatoire'], champ: 'Adresse';

Résumé

J'espère que cet article vous a donné une bonne idée de la façon dont vous pouvez appliquer des promesses à de vieux problèmes et construire votre propre cadre autour d'eux. L’approche basée sur Promise est une solution fantastique pour les opérations abstraites qui peuvent ou non être exécutées de manière synchrone. Vous pouvez également chaîner des rappels et même composer des promesses d'ordre supérieur à partir d'un ensemble d'autres promesses..

Le modèle de promesse est applicable dans une variété de scénarios, et nous espérons en rencontrer quelques-uns et voir une correspondance immédiate.!


Références

  • Promises / A + spec
  • jQuery.Deferred ()
  • Q
  • je promets