Op beloning gebaseerde validatie

Het concept van "Promises" heeft de manier veranderd waarop we asynchrone JavaScript schrijven. Het afgelopen jaar hebben veel frameworks een vorm van het Promise-patroon opgenomen om asynchrone code eenvoudiger te maken, te lezen en te onderhouden. Bijvoorbeeld, jQuery heeft $ .Deferred () toegevoegd en NodeJS heeft de Q- en jspromise-modules die zowel op client als op server werken. Client-side MVC-frameworks, zoals EmberJS en AngularJS, implementeren ook hun eigen versies van Promises.

Maar het hoeft daar niet te stoppen: we kunnen oudere oplossingen heroverwegen en beloften op hen toepassen. In dit artikel doen we precies dat: een formulier valideren met behulp van het Promise-patroon om een ​​supereenvoudige API te ontmaskeren.


Wat is een belofte?

Beloften stellen het resultaat van een operatie bekend.

Simpel gezegd, belooft het resultaat van een operatie. Het resultaat kan een succes of een mislukking zijn en de bewerking zelf kan alles zijn wat een eenvoudig contract handhaaft. Ik koos ervoor om het woord te gebruiken contract omdat je dit contract op verschillende manieren kunt ontwerpen. Gelukkig bereikte de ontwikkelingsgemeenschap een consensus en creëerde een specificatie met de naam Promises / A+.

Alleen de operatie weet echt wanneer deze is voltooid; als zodanig is het verantwoordelijk voor het melden van zijn resultaat met behulp van het Promises / A + contract. Met andere woorden, het beloften om u het eindresultaat na voltooiing te vertellen.

De bewerking levert a belofte object en u kunt uw callbacks eraan koppelen met behulp van de gedaan() of fail () methoden. De operatie kan de uitkomst ervan melden door te bellen promise.resolve () of promise.reject (), respectievelijk. Dit wordt weergegeven in de volgende afbeelding:


Het gebruik van beloften voor formuliervalidatie

Laat me een plausibel scenario schilderen.

We kunnen oudere oplossingen heroverwegen en beloften op hen toepassen.

Formulieren valideren aan de kant van de klant begint altijd met de eenvoudigste intenties. Mogelijk hebt u een aanmeldingsformulier met Naam en E-mail velden en u moet ervoor zorgen dat de gebruiker geldige invoer voor beide velden levert. Dat lijkt redelijk eenvoudig en u begint met de implementatie van uw oplossing.

U krijgt vervolgens te horen dat e-mailadressen uniek moeten zijn en u besluit het e-mailadres op de server te valideren. Dus, de gebruiker klikt op de knop Verzenden, de server controleert de uniciteit van de e-mail en de pagina wordt vernieuwd om eventuele fouten weer te geven. Dat lijkt de juiste aanpak, toch? Nee. Uw klant wil een gladde gebruikerservaring; bezoekers zouden eventuele foutmeldingen moeten zien zonder de pagina te verversen.

Je formulier heeft de Naam veld dat geen ondersteuning aan de server vereist, maar dan heb je de E-mail veld waarvoor u een aanvraag moet indienen bij de server. Serveraanvragen betekent $ .Ajax () oproepen, zodat u e-mailvalidatie in uw callback-functie moet uitvoeren. Als uw formulier meerdere velden heeft waarvoor ondersteuning op de server nodig is, is uw code een geneste puinhoop van $ .Ajax () roept callbacks in. Terugbellen binnen callbacks: "Welkom bij callback hell! We hopen dat je een ellendig verblijf hebt!".

Hoe gaan we om met callback hell?

De oplossing die ik beloofde

Neem een ​​stap terug en denk na over dit probleem. We hebben een aantal bewerkingen die kunnen slagen of falen. Een van deze resultaten kan worden vastgelegd als een Belofte, en de bewerkingen kunnen van alles zijn, van eenvoudige cheques aan de kant van de klant tot complexe validaties aan de serverzijde. Beloften geven u ook het extra voordeel van consistentie, en laten u voorkomen dat u voorwaardelijk controleert op het type validatie. Laten we eens kijken hoe we dit kunnen doen.

Zoals ik eerder heb opgemerkt, zijn er verschillende beloftevolle implementaties in het wild, maar ik zal me concentreren op de implementatie van $ .Deferred () Promise van jQuery.

We zullen een eenvoudig valideringsraamwerk bouwen waarbij elke cheque onmiddellijk een resultaat of een belofte oplevert. Als gebruiker van dit framework hoeft u slechts één ding te onthouden: "het geeft altijd een belofte terug". Laten we beginnen.

Validator Framework met behulp van Promises

Ik denk dat het eenvoudiger is om de eenvoud van Promises te waarderen vanuit het oogpunt van de consument. Laten we zeggen dat ik een formulier heb met drie velden: naam, e-mailadres en adres:

 

Ik zal eerst de validatiecriteria met het volgende object configureren. Dit dient ook als de API van ons framework:

 var validationConfig = '.name': checks: 'required', field: 'Name', '.email': checks: ['verplicht'], field: 'Email', '.address':  controles: ['willekeurig', 'verplicht'], veld: 'Adres';

De sleutels van dit configuratieobject zijn jQuery-selectors; hun waarden zijn objecten met de volgende twee eigenschappen:

  • checks: een reeks of reeks validaties.
  • veld-: de voor de mens leesbare veldnaam, die zal worden gebruikt voor het melden van fouten voor dat veld

We kunnen onze validator noemen, blootgesteld als de globale variabele V, zoals dit:

 V.validate (validationConfig) .done (function () // Success) .fail (functie (fouten) // Validaties mislukt, fouten bevatten de details);

Let op het gebruik van de gedaan() en fail () callbacks; dit zijn de standaard callbacks voor het overhandigen van het resultaat van een Promise. Als we toevallig meer formuliervelden toevoegen, kunt u eenvoudigweg het validationConfig object zonder de rest van de setup te verstoren (het open-gesloten principe in actie). In feite kunnen we andere validaties toevoegen, zoals de uniekheidsbeperking voor e-mailadressen, door het validatorraamwerk uit te breiden (wat we later zullen zien).

Dus dat is de consumentgerichte API voor het validatorraamwerk. Laten we nu duiken en zien hoe het werkt onder de motorkap.

Validator, onder de motorkap

De validator wordt getoond als een object met twee eigenschappen:

  • type: bevat de verschillende soorten validaties, en het dient ook als uitbreidingspunt voor het toevoegen van meer.
  • bevestigen: de kernmethode die de validaties uitvoert op basis van het opgegeven configuratieobject.

De algemene structuur kan worden samengevat als:

 var V = (functie ($) var validator = / * * Uitbreidingspunt - voeg gewoon toe aan deze hash * * V.type ['my-validator'] = * ok: function (value) return true; , * bericht: 'Foutmelding voor mijn-validator' * * / type: 'verplicht': ok: functie (waarde) // is geldig?, bericht: 'Dit veld is verplicht', ... , / ** * * @param config * * '': string | object | [string] * * / validate: function (config) // 1. Normaliseer het configuratieobject // 2. Converteer elke validatie naar een belofte // 3. Verpak een masterbelofte // 4. Retourneer de hoofdbelofte ; ) (JQuery);

De bevestigen methode biedt de onderbouwing van dit kader. Zoals te zien is in de bovenstaande opmerkingen, zijn er vier stappen die hier plaatsvinden:

1. Normaliseer het configuratieobject.

Dit is waar we ons configuratieobject doorlopen en dit in een interne representatie omzetten. Dit is meestal om alle informatie vast te leggen die we nodig hebben om de validatie uit te voeren en indien nodig fouten te melden:

 function normalizeConfig (config) config = config || ; var validaties = []; $ .each (config, function (selector, obj) // maak een array voor vereenvoudigde controle var checks = $ .isArray (obj.checks)? obj.checks: [obj.checks]; $ .each (checks, function (idx, check) validations.push (control: $ (selector), check: getValidator (check), checkName: check, field: obj.field););); terugkeer validaties;  functie getValidator (type) if ($ .type (type) === 'string' && validator.type [type]) return validator.type [type]; return validator.noCheck; 

Deze code doorloopt de sleutels in het configuratieobject en maakt een interne representatie van de validatie. We zullen deze weergave gebruiken in de bevestigen methode.

De getValidator () helper haalt het validator-object op uit de type hash. Als we er geen vinden, geven we de nocheck validator die altijd waar retourneert.

2. Converteer elke validatie naar een belofte.

Hier zorgen we ervoor dat elke validatie een belofte is door de retourwaarde van te controleren validation.ok (). Als het de bevat dan() methode, we weten dat het een belofte is (dit is volgens de beloften / A + spec). Als dat niet het geval is, maken we een ad-hocbelofte die wordt opgelost of afgewezen, afhankelijk van de geretourneerde waarde.

 validate: function (config) // 1. Normaliseer het configuratie-object config = normalizeConfig (config); var beloften = [], controles = []; // 2. Converteer elke validatie naar een belofte $ .each (config, function (idx, v) var value = v.control.val (); var retVal = v.check.ok (value); // Make a belofte, check is gebaseerd op Promises / A + spec if (retVal.then) promises.push (retVal); else var p = $ .Deferred (); if (retVal) p.resolve (); else p.reject (); promises.push (p.promise ()); checks.push (v);); // 3. Word een meesterbelofte // 4. Keer terug naar de hoofdbelofte

3. Word een meesterbelofte.

We hebben een aantal beloften gemaakt in de vorige stap. Wanneer ze allemaal slagen, willen we een van de problemen oplossen of een fout maken met gedetailleerde foutinformatie. We kunnen dit doen door alle beloften in een enkele belofte in te pakken en het resultaat te verspreiden. Als alles goed gaat, lossen we gewoon de hoofdbelofte op.

Voor fouten kunnen we lezen van onze interne validatie-representatie en deze gebruiken voor rapportage. Omdat er meerdere validatiefouten kunnen zijn, lopen we over de beloften array en lees de staat() resultaat. We verzamelen alle afgewezen beloftes in de mislukt array en call verwerpen () op de hoofdbelofte:

 // 3. Omvat een meesterbelofte var masterPromise = $ .Deferred (); $ .when.apply (null, belooft) .done (function () masterPromise.resolve ();) .fail (function () var failed = []; $ .each (belooft, functie (idx, x) if (x.state () === 'rejected') var failedCheck = checks [idx]; var error = check: failedCheck.checkName, error: failedCheck.check.message, field: failedCheck.field, control: failedCheck.control; failed.push (error);); masterPromise.reject (failed);); // 4. Retourneer de hoofdbelofte return masterPromise.promise ();

4. Breng de hoofdbelofte terug.

Uiteindelijk keren we de hoofdbelofte terug van de valideren () methode. Dit is de belofte waarop de clientcode de gedaan() en fail () callbacks.

De stappen twee en drie vormen de kern van dit raamwerk. Door de validaties te normaliseren in een belofte, kunnen we ze consistent aan. We hebben meer controle met een master Promise-object en we kunnen aanvullende contextuele informatie toevoegen die nuttig kan zijn voor de eindgebruiker.


De Validator gebruiken

Zie het demo-bestand voor een volledig gebruik van het validatorraamwerk. Wij gebruiken de gedaan() terugbellen om succes te melden en fail () om een ​​lijst met fouten weer te geven voor elk van de velden. De onderstaande schermafbeeldingen tonen de status van succes en mislukking:

De demo gebruikt dezelfde HTML- en validatieconfiguratie als eerder vermeld in dit artikel. De enige toevoeging is de code die de waarschuwingen weergeeft. Let op het gebruik van de gedaan() en fail () callbacks om de validatieresultaten af ​​te handelen.

 functie showAlerts (fouten) var alertContainer = $ ('. alert'); $ ( 'Error') te verwijderen ().; if (! errors) alertContainer.html ('Alles geslaagd');  else $ .each (errors, function (idx, err) var msg = $ ('') .addClass (' error ') .text (err.error); . Err.control.parent () toevoegen (msg); );  $ ('. validate'). klik (functie () $ ('. indicator'). show (); $ ('. alert'). empty (); V.validate (validationConfig) .done (functie () $ ('. indicator'). hide (); showAlerts ();) .fail (functie (fouten) $ ('. indicator'). hide (); showAlerts (errors);); );

Uitbreiding van de validator

Ik heb eerder gezegd dat we meer validatiebewerkingen aan het framework kunnen toevoegen door de validators uit te breiden type hash. Houd rekening met de willekeurig validator als een voorbeeld. Deze validator slaagt willekeurig of mislukt. Ik weet dat het geen bruikbare validator is, maar het is de moeite waard om enkele van zijn concepten te noteren:

  • Gebruik setTimeout () om de validatie async te maken. Je kunt dit ook zien als het simuleren van netwerklatentie.
  • Een belofte teruggeven van de OK() methode.
 // Uitbreiden met een willekeurige validator V.type ['willekeurig'] = ok: functie (waarde) 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.' ;

In de demo heb ik deze validatie gebruikt op de Adres veld als volgt:

 var validationConfig = / * cilpped voor beknoptheid * / '.address': checks: ['random', 'required'], field: 'Address';

Samenvatting

Ik hoop dat dit artikel je een goed idee heeft gegeven van hoe je Beloften kunt toepassen op oude problemen en je eigen kader om hen heen kunt bouwen. De op beloften gebaseerde aanpak is een fantastische oplossing voor abstracte bewerkingen die al dan niet synchroon lopen. Je kunt ook callbacks koppelen en zelfs hogere beloften samenstellen uit een reeks andere beloften.

Het patroon van de belofte is van toepassing in verschillende scenario's, en u zult er hopelijk enkele tegenkomen en een onmiddellijke match zien!


Referenties

  • Beloften / A + spec
  • jQuery.Deferred ()
  • Q
  • jspromise