Promise-Based Validation

Begrepet "Promises" har endret måten vi skriver asynkron JavaScript på. I løpet av det siste året har mange rammer innarbeidet en form for Promise-mønsteret for å gjøre asynkron kode enklere å skrive, lese og vedlikeholde. For eksempel har jQuery lagt til $ .Deferred (), og NodeJS har Q- og jspromise-modulene som fungerer på både klient og server. Klient-side MVC rammer, som EmberJS og AngularJS, implementerer også sine egne versjoner av løfter.

Men det trenger ikke å stoppe der: vi kan revurdere eldre løsninger og bruke løfter til dem. I denne artikkelen vil vi bare gjøre det: validere et skjema ved hjelp av Promise-mønsteret for å avsløre en super enkel API.


Hva er et løfte?

Løfter gir melding om resultatet av en operasjon.

Enkelt sagt, Løfter gir melding om resultatet av en operasjon. Resultatet kan være en suksess eller en feil, og selve operasjonen kan være alt som overholder en enkel kontrakt. Jeg valgte å bruke ordet kontrakt fordi du kan utforme denne kontrakten på flere forskjellige måter. Heldigvis kom utviklingssamfunnet til enighet og opprettet en spesifikasjon som heter Promises / A+.

Bare operasjonen vet virkelig når den har fullført; Som sådan er det ansvar for å varsle resultatet ved hjelp av Promises / A + kontrakten. Med andre ord, det løfter for å fortelle deg det endelige resultatet etter ferdigstillelse.

Operasjonen returnerer a love objekt, og du kan feste tilbakekallingene til det ved å bruke gjort () eller mislykkes () metoder. Operasjonen kan varsle resultatet ved å ringe promise.resolve () eller promise.reject (), henholdsvis. Dette er avbildet i følgende figur:


Bruke løfter for form validering

La meg male et troverdig scenario.

Vi kan revurdere eldre løsninger og bruke løfter til dem.

Valideringen av klient-side formularer begynner alltid med de enkleste intensjoner. Du kan ha et påmeldingsskjema med Navn og e-post felt, og du må sørge for at brukeren gir gyldig inngang for begge feltene. Det virker ganske greit, og du begynner å implementere løsningen.

Du blir da fortalt at e-postadresser må være unike, og du bestemmer deg for å validere e-postadressen på serveren. Så klikker brukeren på send-knappen, serveren sjekker e-postens unikhet og siden oppdateres for å vise eventuelle feil. Det virker som den riktige tilnærmingen, ikke sant? Nei. Din klient ønsker en smidig brukeropplevelse; Besøkende bør se eventuelle feilmeldinger uten å forfriskne siden.

Skjemaet ditt har Navn felt som ikke krever støtte for server-side, men da har du e-post feltet som krever at du gjør en forespørsel til serveren. Serverforespørsler betyr $ .Ajax () samtaler, så du må utføre e-postvalidering i tilbakeringingsfunksjonen. Hvis skjemaet ditt har flere felt som krever serverstøtte, vil koden din være et nestet rot av $ .Ajax () samtaler i tilbakeringinger. Tilbakeringinger inne callbacks: "Velkommen til å ringe helvete! Vi håper du har et elendig opphold!".

Så, hvordan håndterer vi tilbakeringing helvete?

Løsningen jeg lovet

Ta et skritt tilbake og tenk på dette problemet. Vi har et sett med operasjoner som enten kan lykkes eller mislykkes. Enten av disse resultatene kan bli tatt som en Love, og operasjonene kan være alt fra enkle klientsidekontroller til komplekse server-side-valideringer. Løfter gir deg også den ekstra fordelen av konsistens, samt lar deg unngå betinget kontroll av typen validering. Lar se hvordan vi kan gjøre dette.

Som jeg nevnte tidligere, er det flere løfteimplementeringer i naturen, men jeg vil fokusere på jQuery's $ .Deferred () Promise implementation.

Vi vil bygge en enkel validering rammeverk der hver sjekk umiddelbart returnerer enten et resultat eller et løfte. Som bruker av dette rammeverket må du bare huske en ting: "det returnerer alltid et løfte". La oss komme i gang.

Validator Framework ved hjelp av løfter

Jeg synes det er lettere å sette pris på enkelheten av løfter fra forbrukerens synspunkt. La oss si at jeg har et skjema med tre felt: Navn, E-post og adresse:

 

Jeg vil først konfigurere valideringskriteriene med følgende objekt. Dette fungerer også som vår rammeverkets API:

 var validationConfig = '.name': sjekker: 'obligatorisk', felt: 'Navn', '.email': sjekker: ['required'], felt: 'Email', '.address' sjekker: ['tilfeldig', 'nødvendig'], felt: 'Adresse';

Nøklene til denne konfigurasjonsobjektet er jQuery selectors; deres verdier er objekter med følgende to egenskaper:

  • sjekker: en streng eller en rekke valideringer.
  • felt: Det menneskelige lesbare feltnavnet, som vil bli brukt til rapportering av feil for det aktuelle feltet

Vi kan ringe vår validator, eksponert som den globale variabelen V, som dette:

 V.validate (validationConfig) .done (function () // Suksess) .fail (funksjon (feil) // Validations failed. Errors har detaljene);

Legg merke til bruken av gjort () og mislykkes () callbacks; Dette er standard tilbakeringinger for å levere et løfte resultat. Hvis vi tilfeldigvis legger til flere skjemafelter, kan du bare øke validationConfig objekt uten å forstyrre resten av oppsettet (Open-Closed Principle in action). Faktisk kan vi legge til andre valideringer, som den unike begrensningen for e-postadresser, ved å utvide validatorrammen (som vi vil se senere).

Så det er forbruker-overfor API for validator rammeverket. Nå, la oss dykke inn og se hvordan det fungerer under hetten.

Validator, under hetten

Valideringen er eksponert som et objekt med to egenskaper:

  • type: inneholder de ulike typene valideringer, og det tjener også som utvidelsessted for å legge til flere.
  • validere: Kjernemetoden som utfører valideringene basert på det angitte konfigurasjonsobjektet.

Den samlede strukturen kan oppsummeres som:

 var V = (funksjon ($) var validator = / * * Extension punkt - bare legg til denne hasen * * V.type ['my-validator'] = * ok: funksjon (verdi) return true; , * melding: 'Feilmelding for min-validator' * * / type: 'Required': ok: funksjon (verdi) // er gyldig?, melding: 'Dette feltet er påkrevet', ... , / ** * * @param config * * '': streng | objekt | [string] * * / validate: function (config) // 1. Normaliser konfigurasjonsobjektet // 2. Konverter hver validering til et løfte // 3. Pakke inn i et mesterløfte // 4. Returner masterloftet ; ) (JQuery);

De validere Metoden gir grunnlaget for denne rammen. Som det fremgår av kommentarene ovenfor, er det fire trinn som skjer her:

1. Normaliser konfigurasjonsobjektet.

Her går vi gjennom vårt konfigurasjonsobjekt og konverterer det til en intern representasjon. Dette er for det meste å fange all den informasjonen vi trenger for å utføre valideringen og rapportere feil hvis det er nødvendig:

 fungere normalizeConfig (config) config = config || ; var valideringer = []; $ .each (config, function (selector, obj) // lage en matrise for forenklet kontroll var sjekker = $ .isArray (obj.checks)? obj.checks: [obj.checks]; $ .each (sjekker, funksjon (idx, sjekk) validations.push (control: $ (selector), sjekk: getValidator (sjekk), sjekk navn: sjekk, felt: obj.field););; returnere valideringer;  funksjon getValidator (type) hvis ($ .type (type) === 'streng' && validator.type [type]) returner validator.type [type]; returnere validator.noCheck; 

Denne koden løper over tastene i config-objektet og oppretter en intern representasjon av valideringen. Vi vil bruke denne representasjonen i validere metode.

De getValidator () hjelperen henter valideringsobjektet fra type hash. Hvis vi ikke finner en, returnerer vi noCheck validator som alltid returnerer sant.

2. Konverter hver validering til et løfte.

Her sikrer vi at alle valideringer er et løfte ved å sjekke returverdien til validation.ok (). Hvis den inneholder deretter() metode, vi vet at det er et løfte (dette er i henhold til løftene / A + -spesifikasjonen). Hvis ikke, oppretter vi et ad hoc-løfte som løser eller avviser, avhengig av returverdi.

 validere: funksjon (config) // 1. Normaliser konfigurasjonsobjektet config = normalizeConfig (config); var løfter = [], sjekker = []; // 2. Konverter hver validering til et løfte $ .each (config, funksjon (idx, v) var verdi = v.control.val (); var retVal = v.check.ok (verdi); // Lag en lover, sjekk er basert på Promises / A + spec hvis (retVal.then) promises.push (retVal); annet var p = $ .Deferred (); hvis (retVal) p.resolve (); ellers p.reject (); promises.push (p.promise ()); checks.push (v);); // 3. Pakke inn i et mesterløfte // 4. Returner mesterløftet

3. Fest inn i et mesterløfte.

Vi opprettet en rekke løfter i forrige trinn. Når de alle lykkes, vil vi enten løse en gang eller mislykkes med detaljert feilinformasjon. Vi kan gjøre dette ved å pakke alle løftene inn i et enkelt løfte og forplante resultatet. Hvis alt går bra, løser vi bare på mesterløftet.

For feil kan vi lese fra vår interne valideringsrepresentasjon og bruke den til rapportering. Siden det kan være flere valideringsfeil, løp vi over løfter array og lese stat() resultat. Vi samler alle de avviste løftene inn i mislyktes array og samtale avvise () på mesterløftet:

 // 3. Pakke inn i et mesterløfte var masterPromise = $ .Deferred (); $ .when.apply (null, løfter) .done (funksjon () masterPromise.resolve ();) .fail (funksjon () var mislyktes = []; $ .each (løfter, funksjon (idx, x) if (x.state () === 'avvist') var failedCheckCheck = sjekker [idx]; var error = sjekk: failedCheck.checkName, feil: failedCheck.check.message, felt: failedCheck.field, control: failedCheck.control; failed.push (feil);); masterPromise.reject (mislyktes);); // 4. Return master løfte returner masterPromise.promise ();

4. Returner hovedløftet.

Endelig returnerer vi mesterløftet fra validere() metode. Dette er løftet som klientkoden setter opp gjort () og mislykkes () callbacks.

Trinn to og tre er kjernen i dette rammeverket. Ved å normalisere valideringene til et løfte kan vi håndtere dem konsekvent. Vi har mer kontroll med et master Promise-objekt, og vi kan legge ved ytterligere kontekstuell informasjon som kan være nyttig for sluttbrukeren.


Bruk Validator

Se demonstrasjonsfilen for full bruk av validatorrammen. Vi bruker gjort () tilbakeringing for å rapportere suksess og mislykkes () for å vise en liste over feil mot hvert av feltene. Skjermbildene nedenfor viser suksess- og sviktstilstandene:

Demoen bruker samme HTML og valideringskonfigurasjon som tidligere nevnt i denne artikkelen. Det eneste tillegget er koden som viser varselene. Legg merke til bruken av gjort () og mislykkes () tilbakeringinger for å håndtere valideringsresultatene.

 funksjon showAlerts (feil) var alertContainer = $ ('alert'); $ ( 'Error') fjerne (.); hvis (! feil) alertContainer.html ('Alle passerte');  ellers $ .each (feil, funksjon (idx, feil) var msg = $ ('') .addClass (' error ') .text (err.error); . Err.control.parent () føye (msg); );  $ ('.valueer'). Klikk (funksjon () $ ('indikator'). vis (); $ ('.varsel'). tom (); V.validere (valideringConfig) .done () $ ('.indikator'). skjul (); showAlerts ();) .fail (funksjon (feil) );

Utvider validatoren

Jeg nevnte tidligere at vi kan legge til flere valideringsoperasjoner i rammen ved å utvide validatorens type hash. Vurder tilfeldig validator som et eksempel. Denne validatoren lykkes tilfeldig eller mislykkes. Jeg vet at det ikke er en nyttig validator, men det er verdt å merke seg noen av konseptene sine:

  • Bruk setTimeout () å gjøre validering async. Du kan også tenke på dette som simulerer nettverkslatens.
  • Returner et løfte fra ok () metode.
 // Utvid med en tilfeldig validator V.type ['random'] = ok: funksjon (verdi) var deferred = $ .Deferred (); setTimeout (funksjon () var result = Math.random () < 0.5; if (result) deferred.resolve(); else deferred.reject(); , 1000); return deferred.promise(); , message: 'Failed randomly. No hard feelings.' ;

I demoen brukte jeg denne valideringen på Adresse felt som det:

 var validationConfig = / * cilpped for brevity * / '.address': sjekker: ['tilfeldig', 'nødvendig'], felt: 'Adresse';

Sammendrag

Jeg håper at denne artikkelen har gitt deg en god ide om hvordan du kan bruke løfter til gamle problemer og bygge dine egne rammer rundt dem. Den løftebaserte tilnærmingen er en fantastisk løsning på abstrakte operasjoner som kanskje eller ikke kan kjøre synkront. Du kan også ringe tilbakekallinger og til og med komponere høyere ordre løfter fra et sett med andre løfter.

Promise-mønsteret gjelder i en rekke scenarier, og du vil forhåpentligvis møte noen av dem og se en umiddelbar kamp!


referanser

  • Løfter / A + spes
  • jQuery.Deferred ()
  • Q
  • jspromise