Întoarceți sarcinile Async cu promisiunile JQuery

Promisiunile sunt o caracteristică jQuery interesantă, care o face briza pentru a gestiona evenimentele asincronice. Acestea vă permit să scrieți apeluri mai clare și mai scurte și să păstrați logica aplicațiilor la nivel înalt, separată de comportamentele de nivel scăzut.

După ce înțelegeți Promisiunile, veți dori să le folosiți pentru tot, de la apelurile AJAX la fluxul UI. Asta-i o promisiune!


Înțelegerea promisiunilor

Odată ce o Promisiune este rezolvată sau respinsă, va rămâne în acel stat pentru totdeauna.

O promisiune este un obiect care reprezintă un eveniment o singură dată, de obicei rezultatul unei sarcini de asincronizare ca un apel AJAX. La început, o Promisiune se află într-un in asteptarea stat. În cele din urmă, fie este hotărât (adică sarcina se face) sau respins (dacă sarcina nu a reușit). Odată ce o Promisiune este rezolvată sau respinsă, ea va rămâne în acel stat pentru totdeauna, iar apelurile sale nu vor mai declanșa niciodată.

Puteți atașa apelări inverse la Promisiune, care se va declanșa atunci când promisiunea este rezolvată sau respinsă. Și puteți adăuga mai multe apeluri ori de câte ori doriți - chiar și după ce promisiunea a fost rezolvată / respinsă! (În acest caz, vor declanșa imediat.)

În plus, puteți combina Promisiunile logic cu noi promisiuni. Acest lucru face ușor să scrieți un cod care spune: "Când s-au întâmplat toate aceste lucruri, faceți acest lucru."

Și asta e tot ce trebuie să știi despre Promisiuni în abstract. Există mai multe implementări JavaScript pentru a alege de la. Cele două cele mai notabile sunt q-ul lui Kris Kowal, bazat pe CommonJS Promises / A spec și jQuery Promises (adăugat în jQuery 1.5). Din cauza ubicuității jQuery, vom folosi implementarea sa în acest tutorial.


Efectuarea de promisiuni cu $ .Deferred

Fiecare Promisiune jQuery începe cu un Deferred. A Deferred este doar o Promisiune cu metode care îi permit proprietarului să o soluționeze sau să o respingă. Toate celelalte Promisiuni sunt copii "doar pentru citire" ale unui Descendent; vom vorbi despre cei din secțiunea următoare. Pentru a crea un Deferred, utilizați $ .Deferred () constructor:

A Deferred este doar o Promisiune cu metode care îi permit proprietarului să o soluționeze sau să o respingă.

 var deferred = nou $ .Deferred (); deferred.state (); // "în așteptare" deferred.resolve (); deferred.state (); // "rezolvat" deferred.reject (); // nici un efect, deoarece Promisiunea a fost deja rezolvată

(Notă privind versiunea: stat() a fost adăugat în jQuery 1.7. În 1.5 / 1.6, utilizați isRejected () și isResolved ().)

Putem obține o promisiune "pură" prin apelarea unui Deferred promisiune() metodă. Rezultatul este identic cu cel amânat, cu excepția faptului că rezolva() și respinge() metodele lipsesc.

 var deferred = nou $ .Deferred (); var promise = deferred.promise (); promise.state (); // "în așteptare" deferred.reject (); promise.state (); // "respins"

promisiune() există o metodă exclusă pentru încapsulare: dacă returnați un Deferred dintr-o funcție, acesta poate fi rezolvat sau respins de apelant. Dar dacă returnați numai Promisiunea pură care corespunde cu acel Deferred, apelantul poate citi doar starea sa și poate atașa apeluri de apel. jQuery însuși ia această abordare, returnând promisiunile pure din metodele sale AJAX:

 var getProducts = $ .get ("/ produse"); gettingProducts.state (); // "în așteptare" getProducts.resolve; // nedefinit

Utilizarea -ING tensionată în numele unei promisiuni arată clar că reprezintă un proces.


Modelarea fluxului UI cu promisiuni

Odată ce ai o promisiune, poți atașa cât mai multe apeluri de apel pe care le folosești Terminat(), eșuează (), și mereu() metode:

 promise.done (function () console.log ("Aceasta se va executa dacă această Promisiune este rezolvată.");); promise.fail (function () console.log ("Aceasta se va executa dacă această Promisiune este respinsă.");); promise.always (function () console.log ("Și aceasta se va executa oricum."););

Notă privind versiunea: mereu() a fost denumit complet() înainte de jQuery 1.6.

Există, de asemenea, o scurtă descriere pentru atașarea tuturor acestor tipuri de apeluri deodată, atunci():

 promise.then (doneCallback, failCallback, alwaysCallback);

Comenzile de returnare sunt garantate pentru a rula în ordinea în care au fost atașate.

Un caz de mare folosință pentru Promises reprezintă o serie de acțiuni potențiale ale utilizatorului. Să luăm de exemplu un formular AJAX de bază. Vrem să ne asigurăm că formularul poate fi trimis numai o singură dată și că utilizatorul primește o confirmare când trimite formularul. În plus, dorim să păstrăm codul care descrie comportamentul aplicației separat de codul care atinge marcajul paginii. Acest lucru va face mult mai ușor testarea unităților și va minimiza cantitatea de cod care trebuie modificată dacă modificăm aspectul paginii noastre.

 // Logica aplicației var submittingFeedback = nou $ .Deferred (); trimitereaFeedback.done (funcția (intrare) $ .post ("/ feedback", intrare);); // DOM (= "feedback") trimiteți (funcția () submittingFeedback.resolve ($ ("textarea", aceasta) .val ()); trimitereaFeedback.done (functie () $ ("container") append ("

multumim pentru feedback-ul dvs!

"););

(Profităm de faptul că au trecut argumentele rezolva()/respinge() sunt transmise în mod verbal fiecărui apel invers.)


Împrumuturile promite din viitor

pipe () returneaza o noua Promisiune care va imita orice Promisiune returnata de la unul dintre pipe () callback.

Codul nostru de formular de feedback arată bine, dar există loc de îmbunătățire a interacțiunii. Mai degrabă decât să presupunem optimist că apelul POST va reuși, ar trebui să indicăm mai întâi că formularul a fost trimis (cu ajutorul unui spinner AJAX, să zicem), apoi să-i spuneți utilizatorului dacă depunerea a reușit sau a eșuat când serverul răspunde.

Putem face acest lucru prin atașarea de apeluri la Promisiunea returnată $ .post. Dar aici este o provocare: trebuie să manipulăm DOM-ul de la acele callback-uri și am jurat să păstrăm codul nostru care atinge DOM din codul logic al aplicației. Cum putem face acest lucru, atunci când promisiunea POST este creată în cadrul unei apelări de logică a aplicației?

O soluție este de a "transmite" evenimentele de rezolvare / respingere din Promisiunea POST la o Promisiune care trăiește în domeniul exterior. Dar cum facem acest lucru fără câteva linii de boilerplate (promise1.done (promise2.resolve);...)? Din fericire, jQuery oferă o metodă exact pentru acest scop: pipe ().

pipe () are aceeași interfață ca și atunci() (Terminat() suna inapoi, respinge() suna inapoi, mereu() suna inapoi; fiecare apel invers este opțional), dar cu o singură diferență crucială: În timp ce atunci() întoarce pur și simplu Promisiunea la care este atașat (pentru înlănțuire), pipe () returneaza o noua Promisiune care va imita orice Promisiune returnata de la unul dintre pipe () callback. Pe scurt, pipe () este o fereastră în viitor, permițându-ne să atașăm comportamente la o promisiune care încă nu există.

Iată-ne pe noi nou si imbunatatit formularul de cod, cu promisiunea noastră POST promisă unei promisiuni numite savingFeedback:

 // Logica aplicației var submittingFeedback = nou $ .Deferred (); var savingFeedback = trimitereaFeedback.pipe (funcția (input) return $ .post ("/ feedback", input);); // DOM (= "feedback") trimiteți (funcția () submittingFeedback.resolve ($ ("textarea", aceasta) .val ()); trimitereaFeedback.done (functie () $ ("container") append ("
");)); savingFeedback.then (funcția () $ (" # container ").

multumim pentru feedback-ul dvs!

"), funcția () $ (" # container "), adăugați ("

A apărut o eroare la contactarea serverului.

"), funcția () $ (" container "). eliminați (" spinner ");

Găsirea intersecției promisiunilor

O parte a geniului promisiunilor este natura lor binară. Deoarece au doar două stări posibile, ele pot fi combinate ca booleani (deși booleani ale căror valori ar putea să nu fie încă cunoscute).

Proiecția echivalentă a intersecției logice (ȘI) este dat de $ .La (). Având o listă de promisiuni, cand() returneaza o noua Promisiune care respecta aceste reguli:

  1. Cand toate din Promisiunile date sunt rezolvate, noua Promisiune este rezolvată.
  2. Cand orice din Promisiunile date este respinsă, noua Promisiune este respinsă.

Ori de câte ori așteptați să apară mai multe evenimente neordonate, ar trebui să luați în considerare utilizarea cand().

Apelurile simultane AJAX reprezintă un caz de utilizare evident:

 $ ( "# Container"). Append (“
"), atunci (funcția () // ambele apeluri AJAX au reușit, funcția () // una din apelurile AJAX nu a reușit, funcția () $ ("container").

Un alt caz de utilizare permite utilizatorului să solicite o resursă care poate sau nu să fie deja disponibilă. De exemplu, să presupunem că avem un widget de chat pe care îl încărcăm cu YepNope (consultați Încărcare ușoară Script cu yepnope.js)

 var încărcareChat = nou $ .Deferred (); yepnope (încărcare: "resurse / chat.js", completă: loadingChat.resolve); var lansareaChat = nou $ .Deferred (); $ ( "# LaunchChat") click (launchingChat.resolve).; lansândChat.done (funcția () $ ("# chatContainer"). adăugați ("
");)); $ .when (încărcareChat, lansareaChat) .done (funcția () $ (" # chatContainer ") eliminați (" spinner ");

Concluzie

Promisiunile s-au dovedit a fi un instrument indispensabil în lupta continuă împotriva codului spaghetti asinc. Prin furnizarea unei reprezentări binare a sarcinilor individuale, ele clarifică logica aplicațiilor și reduc pe baza de bord de urmărire a stării.

Dacă doriți să aflați mai multe despre Promisiuni și alte instrumente pentru păstrarea sănătății dvs. într-o lume tot mai asincronă, verificați ebook-ul meu viitoare: Async JavaScript: Rețete pentru codul determinat de eveniment (expirat în martie).

Cod