Conceptul de "Promisiuni" a schimbat modul în care scriem JavaScript asincron. În ultimul an, multe cadre au încorporat o anumită formă a modelului Promise pentru a face codul asincron mai ușor de scris, citit și întreținut. De exemplu, jQuery a adăugat $ .Deferred () și NodeJS are modulele Q și jspromise care funcționează atât pe client, cât și pe server. În cadrul clientului, cadrele MVC, cum ar fi EmberJS și AngularJS, implementează de asemenea propriile versiuni ale promisiunilor.
Dar nu trebuie să ne oprim aici: putem să regândim soluții mai vechi și să le aplicăm Promisiunile. În acest articol, vom face exact acest lucru: validați un formular utilizând modelul Promise pentru a expune un API super simplu.
Promisiunile notifică rezultatul unei operațiuni.
Pur și simplu, promisiunile notifică rezultatul unei operații. Rezultatul poate fi un succes sau un eșec, iar operațiunea, în sine, poate fi orice este conform unui contract simplu. Am ales să folosesc cuvântul contracta pentru că puteți proiecta acest contract în mai multe moduri diferite. Din fericire, comunitatea de dezvoltare a ajuns la un consens și a creat o specificație numită Promises / A+.
Doar operațiunea cunoaște cu adevărat momentul finalizat; ca atare, este responsabil pentru notificarea rezultatului său folosind contractul Promises / A +. Cu alte cuvinte, aceasta promisiuni să vă spunem rezultatul final la finalizare.
Operația returnează a promisiune
obiect și puteți să-i atașați apelurile de apel utilizând butonul Terminat()
sau eșuează ()
metode. Operațiunea își poate notifica rezultatul apelând promise.resolve ()
sau promise.reject ()
, respectiv. Aceasta este ilustrată în figura următoare:
Permiteți-mi să pictez un scenariu plauzibil.
Putem să regândim soluțiile mai vechi și să le aplicăm Promisiunile.
Validarea formularului pe partea clientului începe întotdeauna cu cele mai simple intenții. Este posibil să aveți un formular de înscriere cu Nume și E-mail câmpuri și trebuie să vă asigurați că utilizatorul oferă o intrare valabilă pentru ambele câmpuri. Aceasta pare destul de simplă și începeți să vă implementați soluția.
Apoi, vi se spune că adresele de e-mail trebuie să fie unice și decideți să validați adresa de e-mail de pe server. Deci, utilizatorul face clic pe butonul de trimitere, serverul verifică unicitatea e-mailului și pagina se actualizează pentru a afișa erorile. Asta pare a fi abordarea corectă, nu? Nu. Clientul dvs. dorește o experiență clară a utilizatorului; vizitatorii ar trebui să vadă toate mesajele de eroare fără a reîmprospăta pagina.
Formularul dvs. are Nume câmp care nu necesită niciun suport pentru server, dar apoi aveți E-mail câmpul care vă cere să faceți o cerere către server. Cererile de server înseamnă $ .Ajax ()
apeluri, deci va trebui să efectuați validarea prin e-mail în funcția de apel invers. Dacă formularul dvs. are mai multe câmpuri care necesită asistență pentru server, codul dvs. va fi o problemă imbricată $ .Ajax ()
apeluri în apeluri. Întoarcere înapoi în callback-uri: "Bine ați venit la cheia de apel! Sperăm că aveți un sejur mizerabil!".
Deci, cum ne descurcăm iadul cu apel invers?
Faceți un pas înapoi și gândiți-vă la această problemă. Avem un set de operațiuni care pot reuși sau nu. Fiecare dintre aceste rezultate poate fi capturat ca a Promisiune
, iar operațiile pot fi orice, de la simple verificări de la client la validări complexe de server. Promisiunile vă oferă, de asemenea, avantajul suplimentar al consecvenței, precum și vă permit să evitați verificarea condiționată a tipului de validare. Să vedem cum putem face acest lucru.
După cum am observat mai devreme, există mai multe implementări de promisiuni în sălbăticie, dar mă voi concentra asupra implementării promise a programului jQuery $ .Deferred ().
Vom construi un cadru de validare simplu, în care fiecare cec întoarce imediat fie un rezultat, fie o promisiune. Ca utilizator al acestui cadru, trebuie doar să vă amintiți un lucru: "întoarce mereu o Promisiune". Să începem.
Cred că este mai ușor să apreciezi simplitatea Promisiunilor din punctul de vedere al consumatorului. Să spun că am un formular cu trei câmpuri: Nume, Email și Adresa:
Mai întâi voi configura criteriile de validare cu următorul obiect. Acest lucru servește și ca API a cadrului nostru:
var validationConfig = '.name': verifică: 'obligatoriu', câmp: 'Nume', '.email': checks: verificări: ["aleatoriu", "obligatoriu"], câmp: "Adresa";
Cheile acestui obiect config sunt selectorii jQuery; valorile lor sunt obiecte cu următoarele două proprietăți:
verificări
: un șir sau o serie de validări.camp
: câmpul care poate fi citit de om, care va fi utilizat pentru raportarea erorilor pentru acel câmpNe putem numi validatorul, expus ca variabilă globală V
, asa:
V.validate (validationConfig) .done (funcția () // Success) .fail (funcția (erori) // Validările au eșuat. Erorile au detaliile);
Rețineți utilizarea Terminat()
și eșuează ()
callback; acestea sunt apelurile implicite pentru predarea rezultatelor unui Promise. Dacă se întâmplă să adăugați mai multe câmpuri de formular, puteți doar să măriți validationConfig
obiect fără a perturba restul configurației (Principiul deschis deschis în acțiune). De fapt, putem adăuga și alte validări, cum ar fi constrângerile de unicitate pentru adresele de e-mail, prin extinderea cadrului de validare (pe care îl vom vedea ulterior).
Deci, aceasta este API orientată spre consumator pentru cadrul validator. Acum, hai sa ne scufundam si sa vedem cum functioneaza sub capota.
Validatorul este expus ca obiect cu două proprietăți:
tip
: conține diferite tipuri de validări și servește și ca punct de extensie pentru adăugarea mai multor.valida
: metoda de bază care efectuează validările bazate pe obiectul config furnizat.Structura generală poate fi rezumată astfel:
var V = (function ($) var validator = / * * Punct de extindere - doar adauga la acest hash * * V.type ['my-validator'] = ok: function , * mesaj: 'Mesaj de eroare pentru validatorul meu' * * / type: 'obligatoriu': ok: function (value) // este valid? , / ** * * @param config * * '': șir obiect | [string] * * / validate: function (config) // 1. Normalizeaza obiectul de configurare // 2. Transforma fiecare validare intr-o promisiune // 3. Incheie o promisiune principala // 4. Returneaza promitator principal ; ) (JQuery);
valida
metoda oferă fundamentele acestui cadru. După cum se vede în comentariile de mai sus, există patru pași care se întâmplă aici:
1. Normalizați obiectul de configurare.
Aici mergem prin obiectul config și transformăm-o într-o reprezentare internă. Aceasta este cea mai mare parte pentru a capta toate informațiile de care avem nevoie pentru a efectua validarea și pentru a raporta erorile, dacă este necesar:
funcția normalizeConfig (config) config = config || ; var validations = []; $ .each (config, function (selector, obj) // face o matrice pentru verificarea simplificata var verifica = $ .isArray (obj.checks)? obj.checks: [obj.checks]; $ .each (idx, verificare) validations.push (control: $ (selector), verifica: getValidator (check), checkName: check, field: obj.field);); validări returnate; funcția getValidator (tip) if ($ .type (type) === 'șir' && validator.type [type]) return validator.type [type]; return validator.noCheck;
Acest cod bifează cheile din obiectul config și creează o reprezentare internă a validării. Vom folosi această reprezentare în valida
metodă.
getValidator ()
helper preluă obiectul validator din tip
hash. Dacă nu găsim una, vom returna noCheck
validator care întotdeauna întoarce adevărat.
2. Convertiți fiecare validare într-o Promisiune.
Aici, asigurăm că fiecare validare este o Promisiune prin verificarea valorii returnate a validation.ok ()
. În cazul în care conține atunci()
metodă, știm că este o promisiune (aceasta este conform spec. Promises / A +). Dacă nu, vom crea o Promisiune ad-hoc care se rezolvă sau respinge în funcție de valoarea returnată.
validați: funcția (config) // 1. Normalizați obiectul de configurare config = normalizeConfig (config); var promises = [], verificări = []; // 2. Convertiți fiecare validare la o promisiune $. Fiecare (config, funcția (idx, v) var value = v.control.val (); var retVal = v.check.ok (valoare); promise, verificarea se bazează pe Promises / A + spec dacă (retVal.then) promises.push (retVal); altceva var p = $ .Deferred (); (); promises.push (p.promise ()); checks.push (v);); // 3. Înfășurați într-o promisiune de master // 4. Returnați promisiunea de master
3. Înfășurați-vă într-o promisiune.
Am creat o serie de Promisiuni în pasul anterior. Când toți reușesc, vrem să rezolvăm o dată sau să nu reușim cu informații detaliate despre eroare. Putem face acest lucru prin împachetarea tuturor promisiunilor într-o singură promisiune și propagând rezultatul. Dacă totul merge bine, vom rezolva doar promisiunea de masterat.
Pentru erori, putem citi din reprezentarea noastră de validare internă și o putem folosi pentru raportare. Deoarece pot exista mai multe eșecuri de validare, vom trece peste promisiuni
și citiți stat()
rezultat. Colectăm toate promisiunile respinse în a eșuat
matrice și apel respinge()
pe promisiunea maestrului:
// 3. Încărcați o promisiune de master var masterPromise = $ .Deferred (); $ .when.apply (null, promises) .done (function () masterPromise.resolve (); .fail (function () var failed = []; $ .each (promises, function (idx, x) if (x.state () === "a respins") var failedCheck = verifică [idx]; var error = check: failedCheck.checkName, error: failedCheck.check.message, failedCheck.control; failed.push (error);); masterPromise.reject (eșuat);); // 4. Întoarceți returnul master promise masterPromise.promise ();
4. Întoarceți promisiunea de bază.
În cele din urmă vom reveni la promisiunea de master de la valida()
metodă. Aceasta este Promisiunea pe care codul client configurează Terminat()
și eșuează ()
callback.
Pașii doi și trei sunt esența acestui cadru. Prin normalizarea validărilor într-o Promisiune, le putem trata în mod consecvent. Avem mai mult control cu un obiect master Promise și putem atașa informații contextuale suplimentare care pot fi utile utilizatorului final.
Consultați fișierul demo pentru o utilizare completă a cadrului validator. Noi folosim Terminat()
apel invers pentru a raporta succesul și eșuează ()
pentru a afișa o listă de erori împotriva fiecărui câmp. Capturile de ecran de mai jos prezintă stările de succes și de defecțiuni:
Demo utilizează aceeași configurație HTML și validare menționată mai sus în acest articol. Singura adăugire este codul care afișează alertele. Rețineți utilizarea Terminat()
și eșuează ()
apeluri de apel pentru a gestiona rezultatele validării.
funcția showAlerts (erori) var alertContainer = $ ('. alert'); $ ( 'Eroare') șterge ().; dacă (! erorile) alertContainer.html ('Toți au trecut„); altceva $. fiecare (eroare, funcție (idx, err) var msg = $ ('') .addClass ("eroare") .text (err.error); . Err.control.parent () append (msg); ); $ ('' validate ') .Click (functie () $ ('. () () () () () () () () ($) (). );
Am menționat mai devreme că putem adăuga mai multe operații de validare la cadrul prin extinderea validatorului tip
hash. Considera întâmplător
validator ca exemplu. Acest validator reușește succesiv sau nu reușește. Știu că nu este validator util, dar merită remarcate câteva dintre conceptele sale:
setTimeout ()
pentru a efectua asyncul de validare. Puteți, de asemenea, să vă gândiți la acest lucru ca o simulare a latenței rețelei.O.K()
metodă.// Extindeți cu un validator aleator V.type ['random'] = ok: funcție (valoare) var deferred = $ .Deferred (); setTimeout (funcție () var rezultat = Math.random () < 0.5; if (result) deferred.resolve(); else deferred.reject(); , 1000); return deferred.promise(); , message: 'Failed randomly. No hard feelings.' ;
În demo, am folosit această validare pe Adresa domeniu ca acesta:
var validationConfig = / * cilpped pentru brevity * / '.address': checks: ['random', 'required'], câmp: 'Address';
Sper că acest articol ți-a dat o idee bună despre cum poți aplica promisiunile la problemele vechi și construiești propriul cadru în jurul lor. Abordarea bazată pe promisiune este o soluție fantastică pentru operațiile abstracte care pot sau nu să ruleze sincron. Puteți, de asemenea, să returnați callback-uri și chiar să compuneți Promisiuni de ordin superior dintr-un set de alte Promisiuni.
Modelul de Promisiune este aplicabil într-o varietate de scenarii și, sperăm, veți întâlni unii dintre ei și veți vedea o potrivire imediată!