Et dypdykk i JavaScript promises

Et dypdykk i JavaScript promises

Med ECMAScript 2015 ble promises endelig en del av JavaScript-kjernen. I denne bloggartikkelen skal vi utforske hvordan promises fungerer og se hvordan du kan bruke de til å skrive bedre asynkron kode.

La oss starte med å se et eksempel på hvordan man skriver asynkrone funksjoner i JavaScript uten Promises, og deretter se hvordan vi kan skrive bedre kode med Promises.

Uten promises skriver man gjerne asynkron kode med en kodestil kalt Continuation-Passing Style (CPS). Den fungerer slik at du sender "continuation function" som et argument til din asynkrone funksjon. Du kan kjede et nær sagt ubegrenset antall slike funksjoner etter hverandre.

Her er et eksempel hvor vi henter AJAX-data for en bruker med continuations:

Dette fungerer ganske greit, men det er slik at jo flere continuations du introduserer, dess vanskeligere blir det å opprettholde oversikten over hva som kjøres når. Når det oppstår problemer, for eksempel når readystatus er 4, men en feil har oppstått (request.status er noe annet enn 200), så må du programmere inn feilhåndtering. Det kan bli ganske vrient, særlig om du har flere continuations på rad. La oss se hvordan vi kan løse dette med Promises i stedet.

Når du bruker et promise referer du til en verdi som ikke eksisterer enda, men som vil eksistere på et tidspunkt. Promises har to fortrinn fremfor CPS, For det første frigjør Promises oss fra å skrive en masse kode for å sjekke tilstandsverdier til variabler som inneholder eventuelle og fremtidige verdier, For det andre, gir promises oss muligheten til å komponere vår asynkrone kode funksjonelt og for feil til å boble oppover.

Et Promise befinner seg alltid i en av tre tilstander:

- Ventende (pending). Dette er tilstanden til objektet etter opprettelse. Den befinner seg i denne tilstanden til den enten er fullført eller avvist.
- Fullført (fulfilled). Løftet er fullført og en verdi er tilgjengelig via then.
- Avvist (rejected). Noe gikk galt og ingen verdi vil noensinne bli tilgjengelig.

Promises er thenable. Det vil si at den har en then-metode samt metoder for å fullføre og rapportere om feil. Promises opprettes med en executor-funksjon som aksepterer to argumenter (resolve og reject). Executor kan anses som en koordinator som sørger for at enten resolve eller reject blir kalt.
Når du i Promise-koden kaller på resolver-funksjonen endres tilstanden til fullført. Denne endringen vil utløse eventuelle tilbakekall mot then. Dersom du kaller på rejector-funksjonen endres tilstanden til avvist og utløser likeledes kall mot then. Du kan også håndtere feil i promises med kall mot catch. Når en Promise har endret tilstanden er den ferdig og kan ikke endres igjen. Promises har derfor et snev av immutabilitet over seg, selv om den altså i kjernen har innebygd tilstandsendring.

La oss se på litt kode. I eksempelet under oppretter vi en promise som resolves med en setTimeout etter 1500 millisekunder.

Legg merke til at vi i dette eksempelet ikke kaller på resolver-funksjonen direkte i promise-funksjonen, men sender resolve videre til executor-funksjonen som kalles når setTimeout kjøres.

Vi logget ikke ut noen verdier i dette eksempelet. La oss se hvordan det kan gjøres:

Koden vi har kjørt inntil nå har naivt forventet at ingen feil vil oppstå. Feil kan oppstå på minst to måter: ved at funksjonen oppdager at data den forventer å betjene ikke er som forventet og dersom noe uventet oppstår. I følgende eksempel sier vi fra at det har oppstått en feil og kaller på reject-funksjonen.

Merk at vi tok i bruk catch for å fange opp feil. Det er selvsagt mulig å bygge inn feil og fange det opp med then også. La oss imidlertid se på et eksempel der du helt tilfeldig får enten en resolve eller en reject, og hvordan du fanger dette opp på en elegant måte med bruk av then og catch.

Promises kan som nevnt ikke endre tilstand når den er blitt løst eller avvist. Nedenfor ser du et eksempel på at du kan kalle resolve() eller reject() flere ganger, men tilstanden vil alltid lik det første resolve() eller reject()-kallet.

I enkelte tilfeller kan det være nyttig å kalle på en funksjon utenfor selve promiset, for eksempel når du har en submit/cancel-funksjon. Fordelen med å bruke en promise i det tilfellet er at du kan være sikker på at submit/cancel bare blir kjørt en gang uansett hvor mange ganger brukeren klikker på knappen.

La oss gå til tilbake til start og endre API-kallet til å bruke promises.

Demo på JSBin.

Som vi ser er det enkelt å skjønne hva som forstår og hvordan suksess og feil håndteres. Vi trenger ikke lenger å ta hensyn til readystatus og request.status, men forholder oss til om kallet fungerte eller ikke.

Det kan være du ønsker å kjøre flere promises etter hverandre og ikke reagere før de alle har fullført. Dette gjør du med Promise.all() slik:

Demo på JSBin.

Jeg håper denne gjennomgangen har gitt deg ny innsikt i promises og lyst til å skrive dem. I neste bloggartikkel fra undertegnede skal vi se på generators og blant annet hvordan bruk av disse kombinert med coroutines gjør det enda mer oversiktlig og enklere å skrive asynkron kode med promises.

 

Om bloggeren:
Frontendutvikler med sans for JavaScript. Glad i å snakke om programmering, og har utgitt boken ReactJS Blueprints på Packt forlag.

comments powered by Disqus