Promise-Based Validation

Begreppet "Löften" har förändrat hur vi skriver asynkron JavaScript. Under det gångna året har många ramverk införlivat någon form av Promise-mönstret för att göra asynkron kod enklare att skriva, läsa och underhålla. Till exempel har jQuery lagt till $ .Deferred (), och NodeJS har Q- och jspromise-modulerna som fungerar på både klient och server. Kundernas MVC-ramar, som EmberJS och AngularJS, implementerar också sina egna versioner av löften.

Men det behöver inte sluta där: vi kan ompröva äldre lösningar och tillämpa löften till dem. I den här artikeln gör vi just det: validera en blankett med löftet mönstret för att avslöja ett super enkelt API.


Vad är ett löfte?

Löften meddelar resultatet av en operation.

Enkelt uttryckt, Löften meddelar resultatet av en operation. Resultatet kan vara en framgång eller ett misslyckande, och själva operationen kan vara allt som följer ett enkelt kontrakt. Jag valde att använda ordet kontrakt eftersom du kan utforma detta kontrakt på flera olika sätt. Tack och lov, utvecklingssamhället nått enighet och skapade en specifikation som heter Promises / A+.

Endast operationen vet verkligen när den har slutförts. Som sådan är det ansvaret för att anmäla sitt resultat med hjälp av Promises / A + kontraktet. Med andra ord, det löften för att berätta det slutliga resultatet efter avslutad behandling.

Operationen returnerar a löfte objekt, och du kan bifoga dina återuppringningar till det med hjälp av Gjort() eller misslyckas() metoder. Operationen kan meddela sitt resultat genom att ringa promise.resolve () eller promise.reject (), respektive. Detta är avbildat i följande figur:


Använda löften för formulär validering

Låt mig måla ett rimligt scenario.

Vi kan ompröva äldre lösningar och tillämpa löften till dem.

Valideringen av klientsidans formulär börjar alltid med de enklaste avsikterna. Du kan ha en anmälningsblankett med namn och E-post fält, och du måste se till att användaren ger giltig inmatning för båda fälten. Det verkar ganska enkelt, och du börjar implementera din lösning.

Du får då veta att e-postadresser måste vara unika, och du bestämmer dig för att validera e-postadressen på servern. Så, användaren klickar på Skicka-knappen, servern kontrollerar e-postens unikhet och sidan uppdateras för att visa några fel. Det verkar som rätt tillvägagångssätt, eller hur? Nej. Din klient vill ha en smidig användarupplevelse; Besökare bör se några felmeddelanden utan att uppdatera sidan.

Din blankett har namn fält som inte kräver något support på serversidan, men då har du E-post fält som kräver att du gör en förfrågan till servern. Serverförfrågningar betyder $ .Ajax () samtal, så du måste utföra e-postvalidering i din återuppringningsfunktion. Om din blankett har flera fält som kräver support på serverns sida kommer din kod att vara en kapslad ruta av $ .Ajax () samtal i återuppringningar. Återuppringningar inom callbacks: "Välkommen till callback helvete! Vi hoppas att du har en olycklig vistelse!".

Så, hur hanterar vi callback hell?

Lösningen jag lovade

Ta ett steg tillbaka och tänka på det här problemet. Vi har en uppsättning operationer som antingen kan lyckas eller misslyckas. Endera av dessa resultat kan fångas som en Löfte, och verksamheten kan vara allt från enkla klientsidan kontroller till komplexa server-sida valideringar. Löften ger dig också den ökade fördelen av konsistens, samt låter dig undvika villkorligt kontroll av typen av validering. Låt oss se hur vi kan göra detta.

Som jag noterade tidigare finns det flera löften implementeringar i naturen, men jag kommer att fokusera på jQuerys $ .Deferred () Promise implementation.

Vi kommer att bygga en enkel valideringsram där varje check omedelbart returnerar antingen ett resultat eller ett löfte. Som användare av denna ram behöver du bara komma ihåg en sak: "det returnerar alltid ett löfte". Låt oss börja.

Validator Framework med löften

Jag tycker att det är lättare att uppskatta enkelheten av löften från konsumentens synvinkel. Låt oss säga att jag har en blankett med tre fält: Namn, E-postadress och Adress:

 

Jag kommer först att konfigurera valideringskriterierna med följande objekt. Detta fungerar också som vår ramens API:

 var validationConfig = '.name': check: 'krävs', fält: 'Namn', '.email': checkar [[krävs]], fält: 'Email', '.address' kontroller: ['random', 'required'], fält: 'Adress';

Tangenterna för detta konfigurationsobjekt är jQuery-selektorer; deras värden är föremål med följande två egenskaper:

  • checkar: en sträng eller en rad valideringar.
  • fält: det mänskliga läsbara fältnamnet, som kommer att användas för rapportering av fel för det fältet

Vi kan ringa vår validator, exponerad som den globala variabeln V, så här:

 V.validera (valideringConfig) .done (funktion () // Succes) .fail (funktion (fel) // Validations failed. Errors har detaljerna);

Notera användningen av Gjort() och misslyckas() återanrop; Det här är standardbackbackarna för att lämna ett löftenes resultat. Om vi ​​råkar lägga till fler formulärfält kan du helt enkelt öka validationConfig objekt utan att störa resten av inställningen (det öppna stängda principen i åtgärd). Faktum är att vi kan lägga till andra valideringar, som den unika begränsningen för e-postadresser, genom att utvidga validatorramen (som vi kommer att se senare).

Så det är användargränssnittet för konsumenten för valideringsramen. Nu, låt oss dyka in och se hur det fungerar under huven.

Validator, under huven

Valideraren exponeras som ett objekt med två egenskaper:

  • typ: innehåller de olika typerna av valideringar, och det fungerar också som förlängningspunkt för att lägga till mer.
  • bekräfta: Kärnmetoden som utför valideringarna baserat på det angivna konfigurationsobjektet.

Den övergripande strukturen kan sammanfattas som:

 var V = (funktion ($) var validator = / * * Extensionspunkt - lägg bara till i denna hash * * V.type ['my-validator'] = * ok: funktion (värde) return true; , * meddelande: 'Felmeddelande för min-validator' * * / typ: 'krävs': ok: funktion (värde) // gäller?, meddelande: 'Detta fält är obligatoriskt', ... , / ** * * @param config * * '': sträng | objekt | [string] * * / validate: function (config) // 1. Normalisera konfigurationsobjektet // 2. Konvertera varje validering till ett löfte // 3. Snabba in i ett mästerslöfte // 4. Återgå mästerslovet ; ) (JQuery);

De bekräfta Metod utgör grunden för denna ram. Som framgår av kommentarerna ovan finns det fyra steg som händer här:

1. Normalisera konfigurationsobjektet.

Här går vi igenom vårt konfigurationsobjekt och omvandlar det till en intern representation. Detta är för det mesta att fånga all information vi behöver för att utföra validering och rapportera fel om det behövs:

 funktion normalizeConfig (config) config = config || ; var valideringar = []; $ .each (config, function (selector, obj) // gör en array för förenklad kontroll var kontroller = $ .isArray (obj.checks)? obj.checks: [obj.checks]; $ .each (idx, check) validations.push (control: $ (selector), check: getValidator (check), checkName: check, field: obj.field););); returvalideringar;  funktion getValidator (typ) om ($ .type (typ) === 'sträng' && validator.type [typ]) returnera validator.type [typ]; returnera validator.no check; 

Denna kod loopar över tangenterna i config-objektet och skapar en intern representation av valideringen. Vi kommer att använda denna representation i bekräfta metod.

De getValidator () hjälpen hämtar validatorobjektet från typ hash. Om vi ​​inte hittar någon returnerar vi nocheck validator som alltid returnerar sant.

2. Konvertera varje validering till ett löfte.

Här ser vi till att alla valideringar är ett löfte genom att kontrollera returvärdet av validation.ok (). Om den innehåller sedan() metod, vi vet att det är ett löfte (det här är enligt löften / A + -specifikationen). Om inte, skapar vi ett ad hoc-löfte som löser eller avvisar beroende på returvärdet.

 validera: funktion (config) // 1. Normalisera konfigurationsobjektet config = normalizeConfig (config); var löften = [], checkar = []; // 2. Konvertera varje validering till ett löfte $ .each (config, function (idx, v) var värde = v.control.val (); var retVal = v.check.ok (värde); // Gör en löfte, checken är baserad på Löften / A + spec om (retVal.then) promises.push (retVal); annat var p = $ .Deferred (); om (retVal) p.resolve (); annars p.reject (); promises.push (p.promise ()); checks.push (v);); // 3. Sätta in i ett mästerslöfte // 4. Återgå mästarnas löfte

3. Vik in i en mästare Löfte.

Vi skapade en rad löften i föregående steg. När alla lyckas vill vi antingen lösa en gång eller misslyckas med detaljerad felinformation. Vi kan göra detta genom att förpacka alla löften till ett enda löfte och sprida resultatet. Om allt går bra, löser vi bara på mästarnas löfte.

För fel kan vi läsa från vår interna valideringsrepresentation och använda den för rapportering. Eftersom det kan finnas flera valideringsfel, slingrar vi över löften array och läsa stat() resultat. Vi samlar alla de avvisade löften i misslyckades array och samtal avvisa() på mästarnas löfte:

 // 3. Pak i ett mästerslöfte var masterPromise = $ .Deferred (); $ .when.apply (null, löften) .done (funktion () masterPromise.resolve ();) .fail (funktion () var misslyckades = []; $ .each (löften, funktionen (idx, x) if (x.state () === 'rejected') var failedCheck = kontrollerar [idx]; var error = check: failedCheck.checkName, fel: failedCheck.check.message, fält: failedCheck.field, failedCheck.control; failed.push (error);); masterPromise.reject (misslyckat);); // 4. Återgå huvudloverns löneåterbärarePromise.promise ();

4. Återgå mästarnas löfte.

Slutligen återvänder vi mästarnas löfte från bekräfta() metod. Detta är det löfte som kundkoden ställer in Gjort() och misslyckas() återanrop.

Steg två och tre är kärnpunkten i denna ram. Genom att normalisera valideringen till ett löfte kan vi hantera dem konsekvent. Vi har mer kontroll med ett mästersloverobjekt, och vi kan bifoga ytterligare kontextuell information som kan vara användbar för slutanvändaren.


Använda Validator

Se demoversikten för att använda valideringsramen fullständigt. Vi använder Gjort() återuppringning för att rapportera framgång och misslyckas() för att visa en lista med fel mot vart och ett av fälten. Skärmbilderna nedan visar framgångs och misslyckandena:

Demon använder samma HTML och valideringskonfiguration som tidigare nämnts i den här artikeln. Det enda tillägget är koden som visar varningarna. Notera användningen av Gjort() och misslyckas() återuppringningar för att hantera valideringsresultaten.

 funktion showAlerts (fel) var alertContainer = $ ('alert'); $ ( ". Fel) bort (). om (! fel) alertContainer.html ('Alla passerade');  annat $ .each (fel, funktion (idx, err) var msg = $ ('') .addClass (' error ') .text (err.error); . Err.control.parent () append (msg); );  $ ('.validera'). Klicka på (funktion () $ ('indikator'). visa (); $ ('varning'). () $ ('.indikator'). hide (); showAlerts ();) .fail (funktion) );

Utöka validatorn

Jag nämnde tidigare att vi kan lägga till fler valideringsoperationer till ramverket genom att utvidga validatorns typ hash. Överväga slumpmässig validator som ett exempel. Denna validator slår slumpmässigt eller misslyckas. Jag vet att det inte är en användbar validator, men det är värt att notera några av dess begrepp:

  • Använda sig av setTimeout () att göra validering async. Du kan också tänka på detta som att simulera nätverkslatens.
  • Returnera ett löfte från ok() metod.
 // Förlänga med en slumpmässig validator V.type ['random'] = ok: funktion (värde) var uppskjutet = $ .Veferred (); setTimeout (funktion () var result = Math.random () < 0.5; if (result) deferred.resolve(); else deferred.reject(); , 1000); return deferred.promise(); , message: 'Failed randomly. No hard feelings.' ;

I demo använde jag denna validering på Adress fält som så:

 var validationConfig = / * cilpped för korthet * / '.address': checks: ['random', 'required'], fält: 'Adress';

Sammanfattning

Jag hoppas att den här artikeln har gett dig en bra uppfattning om hur du kan tillämpa löften på gamla problem och bygga dina egna ramar runt dem. Det löftenbaserade tillvägagångssättet är en fantastisk lösning för abstrakta operationer som kanske kan köras synkront. Du kan också koda återuppringningar och till och med komponera högre orderlöften från en uppsättning andra löften.

Löftemönstret är tillämpligt i en mängd olika scenarier, och du kommer förhoppningsvis att stöta på några av dem och se en omedelbar matchning!


referenser

  • Löften / A + spec
  • jQuery.Deferred ()
  • Q
  • jspromise