Eric Elliott

Følg

Jan 23, 2017 · 11 min læse

Foto af Kabun (CC BY-NC-SA-2.,0)

“Master JavaScript Interview” er en serie af indlæg, der er designet til at forberede kandidater til fælles spørgsmål, de er tilbøjelige til at støde på, når du ansøger om et mellem til højt niveau JavaScript position. Dette er spørgsmål, jeg ofte bruger i rigtige intervie .s.

et løfte er et objekt, der kan producere en enkelt værdi et eller andet tidspunkt i fremtiden: enten en løst værdi eller en grund til, at den ikke er løst (f.eks., Et løfte kan være i en af 3 mulige tilstande: opfyldt, afvist eller verserende. Promise brugere kan vedhæfte tilbagekald for at håndtere den opfyldte værdi eller årsagen til afvisning.løfter er ivrige, hvilket betyder, at et løfte vil begynde at udføre den opgave, du giver det, så snart løftekonstruktøren påberåbes. Hvis du har brug for doven, skal du tjekke observerbare eller opgaver.

en ufuldstændig historie med løfter

tidlige implementeringer af løfter og futures (en lignende / relateret id.) begyndte at vises på sprog som MultiLisp og Concurrent Prolog allerede i 1980 ‘ erne., Brugen af ordet “løfte” blev opfundet af Barbara Liskov og Liuba Shrira i 1988.første gang jeg hørte om løfter i JavaScript, var Node helt ny, og samfundet diskuterede den bedste måde at håndtere asynkron adfærd på. Samfundet eksperimenterede med løfter i et stykke tid, men slog sig til sidst på Node-standardfejlen-første tilbagekald.

omkring samme tid tilføjede Dojo løfter via den udskudte API. Voksende interesse og aktivitet førte til sidst til de nyligt dannede løfter/en specifikation designet til at gøre forskellige løfter mere interoperable.,j juerys async-adfærd blev refactored omkring løfter. jQuery ‘s løfte om støtte haft bemærkelsesværdige ligheder Dojo er Udskudt, og det blev hurtigt den mest almindeligt anvendte løfte gennemførelse i JavaScript på grund af jQuery’ s enorme popularitet — for en tid. Det understøttede dog ikke de to kanaler (opfyldt/afvist) kædeadfærd & undtagelsesstyring, som folk regnede med at bygge værktøjer oven på løfter.,

På trods af disse svagheder, jQuery officielt gjort JavaScript løfter mainstream, og bedre stå-alene lover biblioteker som Q, Når, og Bluebird blev meget populær. jQuery ‘ s gennemførelse uoverensstemmelser motiveret nogle vigtige præciseringer i løftet spec, som blev omskrevet og rebranded som Løfter/A+ specifikation.,

ES6 bragt en Løfter/A+ kompatibel Promise global, og nogle meget vigtige Api ‘ er er bygget på toppen af den nye standard Lover støtte: især WHATWG Hente spec og Async Funktioner standard (en fase 3 forslag på tidspunktet for dette skrives).

de her beskrevne løfter er dem, der er kompatible med Promises/a+ – specifikationen, med fokus på ECMAScript-standardenPromise implementering.

hvordan løfter fungerer

et løfte er et objekt, der kan returneres synkront fra en asynkron funktion., Det vil være i en af 3 mulige tilstande:

et løfte afvikles, hvis det ikke verserer (det er løst eller afvist). Nogle gange bruger folk løst og afgjort til at betyde det samme: ikke afventende.

når et løfte er afgjort, kan et løfte ikke genbosættes. Opkald resolve() eller reject() igen vil ikke have nogen effekt. Uforanderligheden af et afgjort løfte er en vigtig funktion.Native JavaScript-løfter udsætter ikke løftestater. I stedet forventes du at behandle løftet som en sort boks., Kun den funktion, der er ansvarlig for at skabe løftet, vil have kendskab til løftestatus eller adgang til at løse eller afvise.

Her er en funktion, der returnerer et løfte, som vil løse efter et angivet tidsrum:

vent — lover eksempel på CodePen

Vores wait(3000) opkald venter 3000ms (3 sekunder), og derefter logge 'Hello!'., Alle spec-kompatible løfter definerer en .then() metode, som du bruger til at videregive handlere, der kan tage den løste eller afviste værdi.

ES6 promise constructor tager en funktion. Denne funktion tager to parametre, resolve(), og reject(). I eksemplet ovenfor bruger vi kun resolve(), så jeg forlod reject() fra parameterlisten. Så kalder vi setTimeout() for at oprette forsinkelsen og kalder resolve() når den er færdig.,

Du kan eventuelt resolve() eller reject() med værdier, som vil blive videregivet til callback funktioner, der er knyttet med .then().

Når jeg reject() med en værdi, passerer jeg altid et Error objekt. Generelt vil jeg have to mulige opløsningsstater: den normale glade sti, eller en undtagelse — alt, hvad der forhindrer den normale glade sti i at ske. Passerer enError objekt gør det eksplicit.,

vigtige Promise regler

en standard for løfter blev defineret af Promises / a+ specification community. Der er mange implementeringer, der er i overensstemmelse med standarden, herunder JavaScript standard ECMAScript løfter.

løfter, der følger specifikationen, skal følge et specifikt sæt regler:

  • et løfte eller “thenable” er et objekt, der leverer en standardkompatibel.then() metode.
  • et verserende løfte kan overgå til en opfyldt eller afvist stat.,
  • et opfyldt eller afvist løfte afvikles og må ikke overgå til nogen anden stat.
  • Når et løfte er afgjort, skal det have en værdi (som kan være undefined). Denne værdi må ikke ændres.

ændring i denne sammenhæng refererer til identitet (===) sammenligning. Et objekt kan bruges som den opfyldte værdi, og objektegenskaber kan mutere.,

Hvert løfte skal levere en .then() metode med følgende underskrift:

promise.then(
onFulfilled?: Function,
onRejected?: Function
) => Promise

.then() metode, skal overholde disse regler:

  • Både onFulfilled() og onRejected() er valgfrit.
  • hvis de angivne argumenter ikke er funktioner, skal de ignoreres.
  • onFulfilled() vil blive kaldt efter løftet er opfyldt, med løftets værdi som det første argument.,
  • onRejected() vil blive kaldt, efter at løftet er afvist, med grunden til afvisning som det første argument. Årsagen kan være enhver gyldig JavaScript-værdi, men fordi afvisninger i det væsentlige er synonyme med undtagelser, anbefaler jeg at bruge Fejlobjekter.
  • hverken onFulfilled() eller onRejected() kan kaldes mere end .n gang.
  • .then() kan kaldes mange gange på samme løfte. Med andre ord kan et løfte bruges til at samle tilbagekald.,
  • .then() skal returnere et nyt løfte, promise2.
  • Hvis onFulfilled() eller onRejected() returnerer en værdi x og x er et løfte, promise2 vil låse ind med (antager den samme tilstand og værdi som) x. Ellers vil promise2 blive opfyldt med værdien af x.,
  • Hvis enten onFulfilled eller onRejected kaster en undtagelse e promise2 forkastes med e som årsag.
  • Hvis onFulfilled er ikke en funktion og promise1 er opfyldt, promise2 skal være opfyldt med den samme værdi som promise1.,
  • Hvis onRejected er ikke en funktion og promise1 afvises, promise2 skal være afvist med samme begrundelse som promise1.

Lover at Kæde

Fordi .then() returnerer altid et nyt løfte, er det muligt at kæde løfter med præcis kontrol over, hvordan og hvor fejl håndteres. Løfter giver dig mulighed for at efterligne normal synkron kode try/catch adfærd.,som synkron kode vil kæde resultere i en sekvens, der kører i seriel., Med andre ord, kan du gøre:

fetch(url)
.then(process)
.then(save)
.catch(handleErrors)
;

Her er et eksempel på en kompleks løfte kæden med flere afvisninger:

Lover at kæde adfærd eksempel på CodePen

Fejl Håndtering

Bemærk, at løfter er både en succes og en fejl handler, og det er meget almindeligt at se kode, der gør dette:

save().then(
handleSuccess,
handleError
);

Men hvad sker der, hvis handleSuccess() kaster en fejl?, Løftet returneret fra .then() vil blive afvist, men der er ikke noget der for at fange afvisningen — hvilket betyder, at en fejl i din app bliver slugt. Ups!

af den grund anser nogle mennesker koden ovenfor for at være et anti-mønster og anbefaler i stedet følgende:

save()
.then(handleSuccess)
.catch(handleError)
;

forskellen er subtil, men vigtig. I det første eksempel vil en fejl med oprindelse i save() – operationen blive fanget, men en fejl med oprindelse i handleSuccess() – funktionen sluges.,

Without .catch(), an error in the success handler is uncaught.

In the second example, .catch() will handle rejections from either save(), or handleSuccess().

With .catch(), both error sources are handled., (diagramkilde)

selvfølgelig kansave() fejlen være en netværksfejl, hvorimodhandleSuccess() fejlen skyldes, at udvikleren glemte at håndtere en bestemt statuskode. Hvad hvis du vil håndtere dem anderledes? Du kan vælge at håndtere dem begge:

save()
.then(
handleSuccess,
handleNetworkError
)
.catch(handleProgrammerError)
;

Uanset hvad du foretrækker, vil jeg anbefale slutter alle lover kæder med en .catch(). Det er værd at gentage:

jeg anbefaler slutter alle lover kæder med en .catch().,

hvordan annullerer jeg et løfte?

en af de første ting, som nye lovende brugere ofte undrer sig over, er, hvordan man annullerer et løfte. Her er en ID.: bare Afvis løftet med “annulleret” som årsagen. Hvis du har brug for at håndtere det anderledes end en “normal” fejl, skal du forgrene dig i din fejlbehandler.

Her er nogle almindelige fejl folk gør, når de ruller deres eget løfte annullering:

tilføjelse .,Annuller () til løftet

tilføjelse af .cancel() gør løftet ikke-standard, men det overtræder også en anden løfteregel: kun den funktion, der opretter løftet, skal være i stand til at løse, afvise eller annullere løftet. At udsætte det bryder indkapslingen og opfordrer folk til at skrive kode, der manipulerer løftet på steder, der ikke burde vide om det. Undgå spaghetti og brudte løfter.

man Glemmer at rydde op

Nogle kloge mennesker har fundet ud af, at der er en måde at bruge Promise.race() som en annullering mekanisme., Problemet med det er, at annulleringskontrol er taget fra den funktion, der skaber løftet, som er det eneste sted, hvor du kan udføre ordentlige oprydningsaktiviteter, såsom rydning af timeouts eller frigørelse af hukommelse ved at rydde henvisninger til data osv…

glemmer at håndtere et afvist Annuller løfte

vidste du, at Chrome kaster advarselsmeddelelser over hele konsollen, når du glemmer at håndtere et løfte afvisning? Ups!

overdrevent kompleks

det tilbagekaldte tc39-forslag til annullering foreslog en separat meddelelseskanal til aflysninger., Det brugte også et nyt koncept kaldet et annulleringstoken. Efter min mening ville løsningen have oppustet løftet spec betydeligt, og den eneste funktion, det ville have givet, at spekulationer ikke direkte understøtter, er adskillelsen af afslag og aflysninger, som IMO ikke er nødvendig til at begynde med.

vil du gerne skifte afhængigt af om der er en undtagelse eller en annullering? Ja, absolut. Er det løftet job? Efter min mening nej, det er det ikke.,

Rethinking løfte annullering

generelt videregiver jeg alle de oplysninger, løftet har brug for for at bestemme, hvordan man løser / afviser / annullerer ved løfte oprettelsestid. På den måde er der ikke behov for en .cancel() metode på et løfte. Du spekulerer måske på, hvordan du muligvis kunne vide, om du vil annullere ved promise creation time eller ej.

“Hvis jeg endnu ikke ved, om jeg skal annullere, hvordan ved jeg, hvad jeg skal videregive, når jeg opretter løftet?,”

Hvis der kun var en slags objekt, der kunne stå ind for en potentiel værdi i fremtiden… Åh, vent.

den værdi, vi videregiver til at repræsentere, om vi vil annullere, kan være et løfte i sig selv. Her er, hvordan det kan se ud:

Opsigelig, vente — prøv det på CodePen

Vi er ved brug af standard parameter opgave at fortælle det, ikke for at annullere som standard. Det gørcancel parameteren bekvemt valgfri., Så sætter vi timeout som vi gjorde før, men denne gang fanger vi timeout ‘ s ID, så vi kan rydde det senere.

Vi bruger cancel.then() metoden til at håndtere annullering og ressourceoprydning. Dette vil kun køre, hvis løftet bliver annulleret, før det har en chance for at løse. Hvis du annullerer for sent, har du savnet din chance. Toget har forladt stationen.

Bemærk: du spekulerer måske på, hvad funktionen noop() er til. Ordet noop står for no-op, hvilket betyder en funktion, der ikke gør noget., Uden det vil V8 kaste advarsler: UnhandledPromiseRejectionWarning: Unhandled promise rejection. Det er en god ide at altid håndtere løfte afslag, selvom din handler er en noop().

Abstrahere Løfte Annullering

Det er fint for en wait() timer, men vi kan abstract gå videre med denne idé til at indkapsle alt, hvad du nødt til at huske:

  1. Afvis annuller løfte af standard — vi ønsker ikke at annullere eller kaste fejl, hvis der ikke annullere love bliver vedtaget på.
  2. Husk at udføre oprydning, når du afviser for aflysninger.,
  3. Husk at onCancel oprydning kan selv kaste en fejl, og den fejl skal også håndteres. (Bemærk, at fejlhåndtering udelades i venteeksemplet ovenfor-det er let at glemme!

lad os oprette et annullerbart løfteværktøj, som du kan bruge til at pakke ethvert løfte., For eksempel, til at håndtere netværk anmodninger osv… underskrift vil se ud som dette:

speculation(fn: SpecFunction, shouldCancel: Promise) => Promise

SpecFunction er ligesom den funktion, du vil passere ind i Promise constructor, med en enkelt undtagelse — det tager en onCancel() handling:

SpecFunction(resolve: Function, reject: Function, onCancel: Function) => Void

Bemærk, at dette eksempel er blot et eksempel for at give dig essensen af, hvordan det virker. Der er nogle andre kant sager, du skal tage i betragtning., I denne version kaldes handleCancel, hvis du annullerer løftet, efter at det allerede er afgjort.

Jeg har implementeret en vedligeholdt produktionsversion af dette med kantsager dækket som open source-biblioteket, spekulation.

lad os bruge den forbedrede biblioteksabstraktion til at omskrive det annullerbare wait() – værktøj fra før., Først installere spekulation:

npm install --save speculation

Nu kan du importere og bruge det:

Dette forenkler tingene lidt, fordi du ikke behøver at bekymre dig om noop(), fange fejl i din onCancel(), funktion eller anden kant tilfælde. Disse detaljer er blevet abstraheret væk af speculation(). Tjek det ud, og brug det gerne i rigtige projekter.,

ekstramateriale af det oprindelige JS-løfte

det oprindeligePromise objekt har nogle ekstra ting, du måske er interesseret i:

  • Promise.reject() returnerer et afvist løfte.
  • Promise.resolve() returnerer et løst løfte.
  • Promise.race() tager et array (eller nogen iterable) og returnerer et løfte, der løser sig med værdien af den første løst løfte i iterable, eller afviser med den begrundelse af de første love, der afviser.,
  • Promise.all() tager et array (eller nogen iterable) og returnerer et løfte, der løser, når alle løfter i iterable argument har løst, eller afviser med den begrundelse, for det første bestået løfte, der afviser.

Konklusion

Løfter er blevet en integreret del af flere udtryk i JavaScript, herunder WHATWG Hente standard, der anvendes for de fleste moderne ajax anmodninger, og Async Funktioner standard, der bruges til at gøre asynkron kode ser synkron.,

Async funktioner er fase 3 på tidspunktet for dette skrives, men jeg forudser, at de vil snart blive en meget populær, meget almindeligt anvendt løsning til asynkron programmering i JavaScript, hvilket betyder, at lære at sætte pris på løfter, kommer til at være endnu mere vigtigt at JavaScript udviklere i den nærmeste fremtid.bruger Redu., foreslår jeg, at du tjekker redu.-saga: et bibliotek, der bruges til at håndtere bivirkninger i Redu., som afhænger af async-funktioner i hele dokumentationen.,

Jeg håber, at selv erfarne promise-brugere har en bedre forståelse af, hvad løfter er, og hvordan de fungerer, og hvordan man bruger dem bedre efter at have læst dette.

Udforsk serien

  • hvad er en lukning?
  • hvad er forskellen mellem klasse og prototypisk arv?
  • hvad er en ren funktion?
  • hvad er Funktionssammensætning?
  • hvad er funktionel programmering?
  • hvad er et løfte?
  • bløde færdigheder

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *