“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:
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()
ogonRejected()
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()
elleronRejected()
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()
elleronRejected()
returnerer en værdix
ogx
er et løfte,promise2
vil låse ind med (antager den samme tilstand og værdi som)x
. Ellers vilpromise2
blive opfyldt med værdien afx
., - Hvis enten
onFulfilled
elleronRejected
kaster en undtagelsee
promise2
forkastes mede
som årsag. - Hvis
onFulfilled
er ikke en funktion ogpromise1
er opfyldt,promise2
skal være opfyldt med den samme værdi sompromise1
., - Hvis
onRejected
er ikke en funktion ogpromise1
afvises,promise2
skal være afvist med samme begrundelse sompromise1
.
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:
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.,
In the second example, .catch()
will handle rejections from either save()
, or handleSuccess()
.
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:
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 ennoop()
.
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:
- Afvis annuller løfte af standard — vi ønsker ikke at annullere eller kaste fejl, hvis der ikke annullere love bliver vedtaget på.
- Husk at udføre oprydning, når du afviser for aflysninger.,
- 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