Wrangle Async-taken met JQuery-beloften

Beloften zijn een opwindende jQuery-functie die het een fluitje van een cent maken om async-gebeurtenissen te beheren. Hiermee kunt u duidelijkere, kortere callbacks schrijven en applicatielogica op hoog niveau gescheiden houden van gedrag op een laag niveau.

Zodra u Beloften begrijpt, wilt u ze gebruiken voor alles van AJAX-aanroepen tot UI-stroom. Dat is een belofte!


Beloften begrijpen

Zodra een belofte is opgelost of afgewezen, blijft deze voor altijd in die staat.

Een belofte is een object dat een eenmalige gebeurtenis vertegenwoordigt, meestal het resultaat van een async-taak zoals een AJAX-aanroep. In eerste instantie zit een belofte in een in afwachting staat. Uiteindelijk is het ook opgelost (wat betekent dat de taak is voltooid) of verworpen (als de taak is mislukt). Zodra een belofte is opgelost of afgewezen, blijft deze voor altijd in die staat en worden callbacks nooit meer opnieuw geactiveerd.

U kunt callbacks koppelen aan de belofte, die wordt geactiveerd wanneer de belofte wordt opgelost of afgewezen. En u kunt meer callbacks toevoegen wanneer u maar wilt, zelfs nadat de belofte is opgelost / afgewezen! (In dat geval vuren ze onmiddellijk af.)

Bovendien kunt u Beloften logisch combineren in nieuwe beloften. Dat maakt het triviaal eenvoudig om code te schrijven die zegt: "Wanneer al deze dingen zijn gebeurd, doe dit dan."

En dat is alles wat u moet weten over Beloften in het abstracte. Er zijn verschillende JavaScript-implementaties om uit te kiezen. De twee meest opvallende zijn q van Kris Kowal, gebaseerd op de CommonJS Promises / A-specificatie en jQuery Promises (toegevoegd in jQuery 1.5). Vanwege de alomtegenwoordigheid van jQuery gebruiken we de implementatie in deze zelfstudie.


Beloften doen met $ .Fouted

Elke jQuery-belofte begint met een uitgestelde actie. Een uitgestelde actie is slechts een belofte met methoden waarmee de eigenaar het probleem kan oplossen of weigeren. Alle andere beloften zijn "alleen-lezen" exemplaren van een uitgestelde; we zullen praten over die in de volgende sectie. Om een ​​uitgesteld te maken, gebruikt u de $ .Deferred () constructor:

Een uitgestelde actie is slechts een belofte met methoden waarmee de eigenaar het probleem kan oplossen of weigeren.

 var deferred = new $ .Deferred (); deferred.state (); // "in afwachting van" deferred.resolve (); deferred.state (); // "opgelost" deferred.reject (); // geen effect, omdat de belofte al was opgelost

(Versie opmerking: staat() is toegevoegd in jQuery 1.7. Gebruik in 1.5 / 1.6 isRejected () en is opgelost().)

We kunnen een "zuivere" belofte krijgen door een uitgestelde te noemen belofte() methode. Het resultaat is identiek aan Uitgesteld, behalve dat de op te lossen () en verwerpen () methoden ontbreken.

 var deferred = new $ .Deferred (); var promise = deferred.promise (); promise.state (); // "in afwachting van" deferred.reject (); promise.state (); // "afgewezen"

De belofte() methode bestaat alleen voor inkapseling: als u een uitgestelde functie retourneert, kan deze door de beller worden opgelost of geweigerd. Maar als u alleen de pure belofte retourneert die overeenkomt met dat uitgestelde, kan de beller alleen de status ervan lezen en callbacks toevoegen. jQuery zelf neemt deze benadering, en retourneert pure beloftes van zijn AJAX-methoden:

 var gettingProducts = $ .get ("/ products"); gettingProducts.state (); // "in behandeling" gettingProducts.resolve; // undefined

De ... gebruiken -ing gespannen in de naam van een belofte maakt duidelijk dat het een proces vertegenwoordigt.


Een UI-flow modelleren met beloftes

Zodra u een belofte hebt, kunt u zoveel callbacks toevoegen als u wilt met behulp van de gedaan(), fail (), en altijd() methoden:

 promise.done (function () console.log ("Dit wordt uitgevoerd als deze belofte is opgelost.");); promise.fail (function () console.log ("Dit zal worden uitgevoerd als deze belofte wordt afgewezen.");); promise.always (function () console.log ("En dit wordt op elke manier uitgevoerd."););

Versie Opmerking: altijd() werd genoemd als compleet() voor jQuery 1.6.

Er is ook een afkorting voor het tegelijkertijd toevoegen van al deze typen callbacks, dan():

 promise.then (doneCallback, failCallback, alwaysCallback);

Terugbelverzoeken worden gegarandeerd uitgevoerd in de volgorde waarin ze zijn bijgevoegd.

Een handig voorbeeld voor Promises is een reeks mogelijke acties van de gebruiker. Laten we een basis AJAX-formulier nemen, bijvoorbeeld. We willen ervoor zorgen dat het formulier slechts één keer kan worden ingediend en dat de gebruiker een bevestiging ontvangt wanneer hij het formulier verzendt. Verder willen we dat de code die het gedrag van de toepassing beschrijft, wordt gescheiden van de code die de opmaak van de pagina raakt. Dit maakt testen van eenheden veel gemakkelijker en minimaliseert de hoeveelheid code die moet worden gewijzigd als we onze pagina-indeling wijzigen.

 // Application logic var submittingFeedback = new $ .Deferred (); submitFeedback.done (functie (invoer) $ .post ("/ feedback", invoer);); // DOM-interactie $ ("# feedback"). Submit (function () submitFeedback.resolve ($ ("textarea", this) .val ()); return false; // voorkomen standaardformuliergedrag); submitback.done (function () $ ("# container"). toevoegen ("

Bedankt voor je feedback!

"););

(We profiteren van het feit dat er argumenten zijn doorgegeven op te lossen ()/verwerpen () worden letterlijk doorgestuurd naar elke callback.)


Lenen van beloften uit de toekomst

pijp() geeft een nieuwe belofte terug die elke belofte zal imiteren die is geretourneerd door een van de pijp() callbacks.

Onze feedbackformuliercode ziet er goed uit, maar er is ruimte voor verbetering in de interactie. In plaats van er optimistisch vanuit te gaan dat onze POST-oproep zal slagen, moeten we eerst aangeven dat het formulier is verzonden (bijvoorbeeld met een AJAX-spinner) en vervolgens aan de gebruiker vertellen of de inzending is geslaagd of mislukt wanneer de server reageert.

We kunnen dit doen door callbacks toe te voegen aan de Promise die wordt geretourneerd door $ .post. Maar daarin ligt een uitdaging: we moeten de DOM van die callbacks manipuleren en we hebben gezworen onze DOM-aanrakende code uit onze applicatie logica code te houden. Hoe kunnen we dat doen, wanneer de POST-belofte wordt gecreëerd binnen een applicatie logica callback?

Een oplossing is om het oplossen / weigeren van evenementen van de POST-belofte naar een belofte door te sturen die in de buitenste scope leeft. Maar hoe doen we dat zonder verschillende lijnen van saaie boilerplate (promise1.done (promise2.resolve);...)? Gelukkig biedt jQuery een methode voor precies dit doel: pijp().

pijp() heeft dezelfde interface als dan() (gedaan() Bel terug, verwerpen () Bel terug, altijd() Bel terug; elke callback is optioneel), maar met één cruciaal verschil: While dan() geeft eenvoudigweg de belofte terug waaraan het is gekoppeld (voor chaining), pijp() geeft een nieuwe belofte terug die elke belofte zal imiteren die is geretourneerd door een van de pijp() callbacks. Kortom, pijp() is een venster op de toekomst, waardoor we gedrag kunnen koppelen aan een belofte die nog niet eens bestaat.

Dit is onze nieuw en verbeterd formuliercode, met onze POST-belofte doorgestuurd naar een belofte genaamd savingFeedback:

 // Application logic var submittingFeedback = new $ .Deferred (); var savingFeedback = submitFeedback.pipe (functie (invoer) return $ .post ("/ feedback", invoer);); // DOM-interactie $ ("# feedback"). Submit (function () submitFeedback.resolve ($ ("textarea", this) .val ()); return false; // voorkomen standaardformuliergedrag); submitback.done (function () $ ("# container"). toevoegen ("
");); savingFeedback.then (function () $ (" # container "). append ("

Bedankt voor je feedback!

");, function () $ (" # container "). append ("

Er is een fout opgetreden bij het contacteren van de server.

");, function () $ (" # container "). remove (". spinner "););

De kruising van beloften vinden

Een deel van het genie van Promises is hun binaire aard. Omdat ze maar twee uiteindelijke staten hebben, kunnen ze worden gecombineerd als booleans (hoewel booleans waarvan de waarden nog niet bekend zijn).

Het Promise-equivalent van de logische kruising (EN) is gegeven door $ .Wanneer (). Gezien een lijst met beloftes, wanneer() geeft een nieuwe belofte terug die voldoet aan deze regels:

  1. Wanneer allemaal van de gegeven beloften zijn opgelost, de nieuwe belofte is opgelost.
  2. Wanneer ieder van de gegeven beloften wordt afgewezen, de nieuwe belofte wordt afgewezen.

Elke keer dat u wacht op meerdere ongeordende gebeurtenissen, moet u overwegen om te gebruiken wanneer().

Gelijktijdige AJAX-oproepen zijn een voor de hand liggend geval:

 $ ( "# Container"). Toevoegen ("
"); $ .when ($. get (" / encrypteData "), $ .get (" / encryptionKey ")). then (function () // beide AJAX-oproepen zijn geslaagd, function () // one van de AJAX-oproepen is mislukt, function () $ ("# container"). remove (". spinner"););

Een ander gebruik is dat de gebruiker een bron kan aanvragen die al dan niet al beschikbaar is. Stel dat we een chatwidget hebben die we laden met YepNope (zie Easy Script Laden met yepnope.js)

 var loadingChat = new $ .Deferred (); yepnope (load: "resources / chat.js", voltooid: loadingChat.resolve); var launchingChat = new $ .Deferred (); $ ( "# LaunchChat"), klikt u op (launchingChat.resolve).; launchingChat.done (function () $ ("# chatContainer"). append ("
");); $ .wanneer (loadingChat, launchChat) .done (functie () $ (" # chatContainer "). remove (". spinner "); // start chat);

Conclusie

Beloften hebben zich bewezen als een onmisbare tool in de voortdurende strijd tegen async spaghetti-code. Door een binaire weergave van afzonderlijke taken te bieden, verduidelijken ze de applicatielogica en verminderen ze de state-tracking boilerplate.

Als je meer wilt weten over Promises en andere hulpmiddelen voor het bewaren van je gezond verstand in een steeds asynchrone wereld, bekijk dan mijn aankomende e-boek: Async JavaScript: Recepten voor Event-Driven Code (gepland in maart).