Wrangle Async-uppdrag med JQuery-löften

Löften är en spännande jQuery-funktion som gör det lätt att hantera async-händelser. De tillåter dig att skriva tydligare, kortare återuppringningar och behålla applikationslogiken på hög nivå separat från lågnivåbeteenden.

När du förstår Löften, vill du använda dem för allt från AJAX-samtal till UI-flöde. Det är ett löfte!


Förstå löften

När ett löfte är löst eller avvisat, kommer det att förbli i det läget för alltid.

Ett löfte är ett objekt som representerar en engångshändelse, vanligtvis resultatet av en async-uppgift som ett AJAX-samtal. Först är ett löfte i en avvaktan stat. Så småningom är det heller löst (vilket innebär att uppgiften är klar) eller avvisade (om uppgiften misslyckades). När ett löfte har lösts eller avvisats, kommer det att förbli i det tillståndet för alltid, och dess återuppringningar kommer aldrig att eldas igen.

Du kan bifoga återuppringningar till löftet, vilket kommer att skjuta när löftet löser eller avvisas. Och du kan lägga till fler återuppringningar när du vill - även efter att löfte har lösts / avvisats! (I så fall kommer de att skjuta omedelbart.)

Dessutom kan du kombinera löften logiskt till nya löften. Det gör det trivialt enkelt att skriva kod som säger "När alla dessa saker har hänt, gör den här andra saken."

Och det är allt du behöver veta om löften i abstrakt. Det finns flera JavaScript-implementeringar att välja mellan. De två mest anmärkningsvärda är Kris Kowals q, baserat på CommonJS Promises / A-specifikationen, och jQuery Promises (läggas till i jQuery 1.5). På grund av jQuery's ubiquity använder vi dess implementering i denna handledning.


Gör löften med $ .Deferred

Varje jQuery-löfte börjar med en uppskjuten. A Utsatt är bara ett löfte med metoder som gör att ägaren kan lösa eller avvisa den. Alla andra löften är "skrivskyddade" kopior av en uppskjuten; vi pratar om dem i nästa avsnitt. För att skapa en uppskjuten, använd $ .Deferred () konstruktör:

A Utsatt är bara ett löfte med metoder som gör att ägaren kan lösa eller avvisa den.

 var uppskjuten = ny $ .Deferred (); deferred.state (); // "väntar" deferred.resolve (); deferred.state (); // "löst" deferred.reject (); // ingen effekt, eftersom löftet redan var löst

(Version notering: stat() tillsattes i jQuery 1.7. I 1,5 / 1,6, använd isRejected () och isResolved ().)

Vi kan få ett "rent" löfte genom att ringa en uppskjuten löfte() metod. Resultatet är identiskt med den uppskjutna, förutom att lösa() och avvisa() metoder saknas.

 var uppskjuten = ny $ .Deferred (); var löfte = uppskjuten.promise (); promise.state (); // "väntar" deferred.reject (); promise.state (); // "avvisade"

De löfte() Metoden existerar endast för inkapsling: Om du returnerar en Uppskjuten från en funktion kan den lösas eller avvisas av den som ringer. Men om du bara returnerar det rena löftet som motsvarar det uppskjutna, kan den som ringer bara läsa sitt tillstånd och bifoga återuppringningar. jQuery själv tar detta tillvägagångssätt, återvänder rena löften från sina AJAX-metoder:

 var gettingProducts = $ .get ("/ products"); gettingProducts.state (); // "väntar" gettingProducts.resolve; // odefinierad

Använda -ing spänd i namnet på ett löfte gör det klart att det utgör en process.


Modellera en UI-flöde med löften

När du har ett löfte kan du bifoga så många callbacks som du vill använda Gjort(), misslyckas(), och alltid() metoder:

 promise.done (function () console.log ("Detta kommer att köras om det här löftet är löst.");); promise.fail (funktion () console.log ("Detta kommer att köras om detta löfte avvisas.");); promise.always (function () console.log ("Och detta kommer att springa på något sätt."););

Version Obs: alltid() hänvisades till som komplett() före jQuery 1.6.

Det finns också en stenografi för att fästa alla dessa typer av samtal på en gång, sedan():

 löfte.then (doneCallback, failCallback, alwaysCallback);

Återuppringningar garanteras att löpa i den ordning de fästs i.

Ett stort användningsfall för Promises representerar en rad potentiella åtgärder av användaren. Låt oss ta en grundläggande AJAX-form, till exempel. Vi vill se till att formuläret endast kan lämnas in en gång och att användaren får viss bekräftelse när de skickar formuläret. Dessutom vill vi hålla koden som beskriver tillämpningens beteende separerad från koden som berör sidans markering. Detta kommer att göra enhetstestning mycket enklare och minimera mängden kod som behöver ändras om vi ändrar vår sidlayout.

 // Application logic var submittingFeedback = new $ .Deferred (); submittingFeedback.done (funktion (inmatning) $ .post ("/ feedback", input);); // DOM-interaktion $ ("# återkoppling"). Skicka (funktion () submittingFeedback.resolve ($ ("textarea", detta) .val ()); returnera false; // förhindra standardformulär beteende); submittingFeedback.done (function () $ ("# container"). append ("

Tack för din feedback!

"););

(Vi utnyttjar det faktum att argument gick vidare till lösa()/avvisa() vidarebefordras ordentligt till varje återuppringning.)


Lånande löften från framtiden

rör() returnerar ett nytt löfte som kommer att efterlikna något löfte returnerat från en av rör() återanrop.

Vår feedback formulär ser bra ut, men det finns utrymme för förbättring av interaktionen. I stället för att optimistiskt anta att vårt POST-samtal kommer att lyckas, bör vi först ange att formuläret har skickats (med en AJAX-spinnare, säg) och sedan berätta för användaren om inlämningen lyckades eller misslyckades när servern svarar.

Vi kan göra detta genom att bifoga callbacks till löftet returnerat av $ .post. Men det ligger en utmaning: Vi måste manipulera DOM från dessa återkallelser, och vi har lovat att behålla vår DOM-touching kod ur vår programlogik kod. Hur kan vi göra det när POST-löfte skapas inom en ansökningslogisk återuppringning?

En lösning är att "vidarebefordra" lösa / avvisa händelserna från POST Promise till ett löfte som lever i det yttre räckhållet. Men hur gör vi det utan flera rader av kyld platta (promise1.done (promise2.resolve);...)? Tack och lov, jQuery ger en metod för exakt detta ändamål: rör().

rör() har samma gränssnitt som sedan() (Gjort() ring tillbaka, avvisa() ring tillbaka, alltid() ring tillbaka; varje återuppringning är valfri), men med en avgörande skillnad: medan sedan() returnerar helt enkelt det löfte det är kopplat till (för kedja), rör() returnerar ett nytt löfte som kommer att efterlikna något löfte returnerat från en av rör() återanrop. Kortfattat, rör() är ett fönster in i framtiden, så att vi kan bifoga beteenden till ett löfte som inte ens existerar än.

Här är vår ny och förbättrad formulär kod, med vår POST Promise piped till ett löfte som heter savingFeedback:

 // Application logic var submittingFeedback = new $ .Deferred (); var savingFeedback = submittingFeedback.pipe (funktion (inmatning) return $ .post ("/ feedback", input);); // DOM-interaktion $ ("# återkoppling"). Skicka (funktion () submittingFeedback.resolve ($ ("textarea", detta) .val ()); returnera false; // förhindra standardformulär beteende); submittingFeedback.done (function () $ ("# container"). append ("
");); savingFeedback.then (funktion () $ (" # container ").

Tack för din feedback!

");, funktion () $ (" # container ").

Det gick inte att kontakta servern.

");, funktion () $ (" # container "). ta bort (" spinner "););

Hitta kryssningen av löften

En del av Promises geni är deras binära natur. Eftersom de bara har två slutliga stater kan de kombineras som booleska (om än booleska vars värden ännu inte är kända).

Löftet motsvarar det logiska korsningen (OCH) ges av $ .När (). Givet en lista över löften, när() returnerar ett nytt löfte som följer dessa regler:

  1. När Allt av de givna löften är löst, det nya löftet är löst.
  2. När några av de givna löften avvisas, det nya löftet avvisas.

När du väntar på att flera oorderade händelser inträffar, bör du överväga att använda när().

Samtidiga AJAX-samtal är ett uppenbart användningsfall:

 $ ( "# Container"). Bifoga ("
$) ($ .when ("/ encryptedData"), $ .get ("/ encryptionKey")). Sedan (funktionen () // båda AJAX-samtalen har lyckats, funktionen av AJAX-samtalen har misslyckats, funktion () $ ("# container"). ta bort ("spinner"););

Ett annat användarfall låter användaren begära en resurs som kanske eller inte redan är tillgänglig. Anta till exempel att vi har en chatt widget som vi laddar med YepNope (se Easy Script Loading med yepnope.js)

 var loadingChat = new $ .Deferred (); yepnope (load: "resources / chat.js", komplett: loadingChat.resolve); var launchingChat = new $ .Deferred (); . ( "# LaunchChat") klickar du på (launchingChat.resolve) $; launchingChat.done (funktion () $ ("# chatContainer"). append ("
");)); $ .when (loadingChat, launchingChat) .done (funktion () $ (" # chatContainer "). ta bort (" spinner "); // start chat);

Slutsats

Löften har visat sig vara ett oumbärligt verktyg i den pågående kampen mot async spaghetti koden. Genom att tillhandahålla en binär representation av enskilda uppgifter, klargör de applikationslogiken och skärs ner på statens spårningsplatta.

Om du vill veta mer om Löften och andra verktyg för att behålla din sanity i en alltmer asynkron värld, kolla in min kommande eBook: Async JavaScript: Recept för Event-Driven Code (utgår i mars).