Wrangle Async-oppgaver med JQuery-løfter

Løfter er en spennende jQuery-funksjon som gjør det til en bris å administrere asynk-hendelser. De tillater deg å skrive klarere, kortere tilbakeringinger og holde applikasjonslogikk på høyt nivå atskilt fra lavt nivå atferd.

Når du forstår løfter, vil du bruke dem til alt fra AJAX-samtaler til brukergrensesnitt. Det er et løfte!


Forstå løfter

Når et løfte er løst eller avvist, vil det forbli i den staten for alltid.

Et løfte er et objekt som representerer en engasjement, vanligvis resultatet av en asynk-oppgave som et AJAX-anrop. I begynnelsen er et løfte i a i påvente av stat. Til slutt er det heller løst (som betyr at oppgaven er ferdig) eller avvist (hvis oppgaven mislyktes). Når et løfte er løst eller avvist, vil det forbli i den tilstanden for alltid, og tilbakekallingen vil aldri brann igjen.

Du kan legge til tilbakeringinger til løftet, som vil brenne når løftet er løst eller avvist. Og du kan legge til flere tilbakeringinger når du vil - selv etter at løftet er løst / avvist! (I så fall brenner de umiddelbart.)

I tillegg kan du kombinere løfter logisk inn i nye løfter. Det gjør det trivielt enkelt å skrive kode som sier "Når alle disse tingene har skjedd, gjør du denne andre tingen."

Og det er alt du trenger å vite om løfter i abstrakt. Det er flere JavaScript-implementeringer å velge mellom. De to mest bemerkelsesverdige er Kris Kowals q, basert på CommonJS Promises / A spec, og jQuery Promises (lagt til i jQuery 1.5). På grunn av jQuery's ubiquity bruker vi implementeringen i denne opplæringen.


Gjør løfter med $ .Deferred

Hvert jQuery løfte begynner med en utsatt. En Utsatt er bare et løfte med metoder som gjør at eieren kan løse eller avvise det. Alle andre løfter er "skrivebeskyttet" kopier av en utsatt; vi snakker om de i neste avsnitt. For å opprette en Utsatt, bruk $ .Deferred () konstruktør:

En Utsatt er bare et løfte med metoder som gjør at eieren kan løse eller avvise det.

 var utsatt = ny $ .Deferred (); deferred.state (); // "venter" deferred.resolve (); deferred.state (); // "løst" deferred.reject (); // ingen effekt, fordi løftet allerede var løst

(Versjonsnotat: stat() ble lagt til i jQuery 1.7. I 1,5 / 1,6, bruk isRejected () og isResolved ().)

Vi kan få et "rent" løfte ved å ringe en utsatt love() metode. Resultatet er identisk med Utsatt, bortsett fra at Løse() og avvise () metoder mangler.

 var utsatt = ny $ .Deferred (); var løfte = deferred.promise (); promise.state (); // "venter" deferred.reject (); promise.state (); // "avvist"

De love() Metoden eksisterer bare for innkapsling: Hvis du returnerer en Utsatt fra en funksjon, kan den bli løst eller avvist av den som ringer. Men hvis du bare returnerer det rene løftet som svarer til det utsatte, kan den som ringer bare lese sin tilstand og legge ved tilbakeringinger. jQuery selv tar denne tilnærmingen, og returnerer rene løfter fra sine AJAX-metoder:

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

Bruker -ing Spent i navnet på et løfte gjør det klart at det representerer en prosess.


Modellering av UI-strømmen med løfter

Når du har et løfte, kan du legge ved så mange tilbakeringinger som du liker å bruke gjort (), mislykkes (), og alltid() metoder:

 promise.done (function () console.log ("Dette vil kjøre hvis dette løftet er løst.");); promise.fail (function () console.log ("Dette vil kjøre hvis dette løftet avvises.");; promise.always (function () console.log ("Og dette vil kjøre uansett."););

Versjon Merknad: alltid() ble referert til som fullstendig() før jQuery 1.6.

Det er også en stenografi for å feste alle disse tilbakekallingene samtidig, deretter():

 Promise.then (doneCallback, failCallback, alwaysCallback);

Tilbakeringinger er garantert å løpe i den rekkefølgen de var tilknyttet.

En stor brukstilfelle for Promises representerer en rekke potensielle handlinger av brukeren. La oss ta et grunnleggende AJAX-skjema, for eksempel. Vi ønsker å sikre at skjemaet kun kan sendes inn en gang, og at brukeren mottar noen bekreftelse når de sender inn skjemaet. Videre vil vi beholde koden som beskriver applikasjonens oppførsel, skilt fra koden som berører sidens markering. Dette vil gjøre enhetstesting mye enklere, og minimere mengden kode som må endres hvis vi endrer sideoppsettet.

 // Programlogikk var submittingFeedback = ny $ .Deferred (); submittingFeedback.done (funksjon (input) $ .post ("/ feedback", input);); // DOM-interaksjon $ ("# tilbakemelding"). Send inn (funksjon () submittingFeedback.resolve ($ ("textarea", dette) .val ()); return false; // forhindre standard formadferdighet); submittingFeedback.done (function () $ ("# container"). append ("

Takk for din tilbakemelding!

"););

(Vi dra nytte av det faktum at argumenter passerte til Løse()/avvise () blir videresendt ordentlig til hver tilbakeringing.)


Lånende løfter fra fremtiden

rør() returnerer et nytt løfte som vil etterligne et løfte returnert fra en av rør() callbacks.

Vår tilbakemeldingskode ser bra ut, men det er rom for forbedring i samspillet. I stedet for optimistisk å anta at vårt POST-anrop lykkes, må vi først indikere at skjemaet er sendt (med en AJAX-spinner, si), og deretter fortell brukeren om innleveringen lyktes eller mislyktes når serveren reagerer.

Vi kan gjøre dette ved å legge ved tilbakeringinger til løftet returnert av $ .post. Men det ligger en utfordring: Vi må manipulere DOM fra disse tilbakekallingene, og vi har lovet å holde vår DOM-berøringskode ut av vår logikkode for program. Hvordan kan vi gjøre det når POST-løftet er opprettet i et programlogikk tilbakeringing?

En løsning er å "videresende" løse / avvise hendelser fra POST-løftet til et løfte som lever i det ytre omfanget. Men hvordan gjør vi det uten flere linjer med kjedelig kjeleplate (promise1.done (promise2.resolve);...)? Heldigvis gir jQuery en metode for akkurat dette formålet: rør().

rør() har samme grensesnitt som deretter() (gjort () Ring tilbake, avvise () Ring tilbake, alltid() Ring tilbake; hver tilbakering er valgfritt), men med en avgjørende forskjell: Mens deretter() bare returnerer løftet det er knyttet til (for kjetting), rør() returnerer et nytt løfte som vil etterligne et løfte returnert fra en av rør() callbacks. Kort oppsummert, rør() er et vindu inn i fremtiden, slik at vi kan knytte oppførsel til et løfte som ikke engang eksisterer ennå.

Her er vår ny og forbedret form kode, med vår POST Promise piped til et løfte som heter savingFeedback:

 // Programlogikk var submittingFeedback = ny $ .Deferred (); var savingFeedback = submittingFeedback.pipe (funksjon (input) return $ .post ("/ feedback", input);); // DOM-interaksjon $ ("# tilbakemelding"). Send inn (funksjon () submittingFeedback.resolve ($ ("textarea", dette) .val ()); return false; // forhindre standard formadferdighet); submittingFeedback.done (function () $ ("# container"). append ("
");); savingFeedback.then (funksjon () $ (" # container "). append ("

Takk for din tilbakemelding!

");, funksjon () $ (" # container "). legge til ("

Det oppsto en feil ved å kontakte serveren.

");, funksjon () $ (" # container "). fjerne (" spinner "););

Finne oppsigelsen av løfter

En del av løftets geni er deres binære natur. Fordi de bare har to tilfeldige stater, kan de kombineres som boolesker (om enn boolesker hvis verdier kanskje ikke er kjent).

Løftet tilsvarer det logiske skjæringspunktet (OG) er gitt av $ .Når (). Gitt en liste over løfter, når() returnerer et nytt løfte som følger disse reglene:

  1. Når alle av de givne løftene er løst, er det nye løftet løst.
  2. Når noen av de givne løftene blir avvist, blir det nye løftet avvist.

Når du venter på at flere uordnede hendelser skal oppstå, bør du vurdere å bruke når().

Samtidig AJAX-anrop er en åpenbar brukstilstand:

 $ ( "# Container"). Append ("
"); $ .when ($. get (" / encryptedData "), $ .get (" / encryptionKey ")), deretter (funksjon () // begge AJAX-anropene har lyktes, funksjon () // en av AJAX-anropene har mislyktes, funksjon () $ ("# container"). fjern ("spinner"););

En annen brukstilfelle gir brukeren mulighet til å be om en ressurs som kanskje eller ikke allerede er tilgjengelig. For eksempel, anta at vi har en chat-widget som vi laster inn med YepNope (se Easy Script Loading med yepnope.js)

 var loadingChat = new $ .Deferred (); yepnope (load: "resources / chat.js", fullfør: loadingChat.resolve); var launchingChat = ny $ .Deferred (); $ ( "# LaunchChat") klikk (launchingChat.resolve.); launchingChat.done (function () $ ("# chatContainer") .append ("
");); $ .when (loadingChat, launchingChat) .done (function () $ (" # chatContainer "). fjern (" spinner "); // start chat);

Konklusjon

Løfter har vist seg å være et uunnværlig verktøy i den pågående kampen mot async spaghetti kode. Ved å gi en binær representasjon av individuelle oppgaver, klargjør de søknadslogikk og kuttes ned på tilstandspåvirkende boilerplate.

Hvis du vil vite mer om løfter og andre verktøy for å bevare din sunnhet i en stadig mer asynkron verden, sjekk ut min kommende eBook: Async JavaScript: Oppskrifter for Event-Driven Code (utløper i mars).