Eric Elliott

följ
23 Jan 2017 · 11 min läs

foto av kabun (cc av NC SA 2.,0)

”behärska JavaScript-intervjun” är en serie inlägg som är utformade för att förbereda kandidater för vanliga frågor som de sannolikt kommer att stöta på när de ansöker om en mid-till-Senior-nivå JavaScript-position. Det här är frågor som jag ofta använder i verkliga intervjuer.

ett löfte är ett objekt som kan ge ett enda värde någon gång i framtiden: antingen ett löst värde eller en anledning till att det inte är löst (t.ex. ett nätverksfel uppstod)., Ett löfte kan vara i ett av 3 möjliga stater: uppfyllt, avvisat eller väntande. Promise-användare kan bifoga callbacks för att hantera det uppfyllda värdet eller orsaken till avslag.

löften är angelägna, vilket innebär att ett löfte kommer att börja göra vilken uppgift du ger det så snart löftet konstruktören åberopas. Om du behöver Lat, kolla in observables eller uppgifter.

en ofullständig historia av löften

tidiga implementeringar av löften och terminer (en liknande / relaterad idé) började dyka upp på språk som MultiLisp och samtidig Prolog så tidigt som 1980-talet., Användningen av ordet ”löfte” myntades av Barbara Liskov och Liuba Shrira 1988.

första gången jag hörde om löften i JavaScript var Node helt ny och diskussionsgruppen diskuterade det bästa sättet att hantera asynkront beteende. Gemenskapen experimenterade med löften ett tag, men så småningom bosatte sig på nod-standardfel-första callbacks.

ungefär samtidigt lade Dojo löften via det uppskjutna API: et. Växande intresse och aktivitet ledde så småningom till de nybildade löften/en specifikation utformad för att göra olika löften mer driftskompatibla.,

jQuery async beteenden refactored runt löften. jQuery ’ s promise support hade anmärkningsvärda likheter med Dojos uppskjutna, och det blev snabbt den vanligaste promise — implementeringen i JavaScript på grund av jQuery enorma popularitet-för en tid. Det stödde dock inte två kanaler (uppfyllt/avvisat) kedjande beteende & undantagshantering som folk räknade med att bygga verktyg ovanpå löften.,

trots dessa svagheter, jQuery officiellt gjort JavaScript löften mainstream, och bättre fristående löfte bibliotek som Q, när, och Bluebird blev mycket populär. jquerys genomförande inkompatibiliteter motiverade några viktiga förtydliganden I promise spec, som omskrivits och omprofilerats som Promises/a+ – specifikationen.,

ES6 gav ett löfte / a + – kompatibeltPromise global, och några mycket viktiga API: er byggdes ovanpå det nya Standardlöfte stödet: i synnerhet WHATWG Fetch spec och async Functions standard (ett steg 3 utkast vid tidpunkten för detta skrivande).

de löften som beskrivs här är de som är kompatibla med Promises/a+ – specifikationen, med fokus på ECMAScript-standarden Promise – implementeringen.

hur löften fungerar

ett löfte är ett objekt som kan returneras synkront från en asynkron funktion., Det kommer att vara i ett av 3 möjliga tillstånd:

ett löfte löses om det inte är väntande (det har lösts eller avvisats). Ibland använder människor löst och fast för att betyda samma sak: inte väntande.

en gång avgjort kan ett löfte inte återställas. Anroparesolve() ellerreject() igen kommer inte att ha någon effekt. Det fasta löftets oföränderlighet är en viktig egenskap.

Native JavaScript-löften utsätter inte promise-stater. Istället förväntas du behandla löftet som en svart låda., Endast den funktion som ansvarar för att skapa löftet kommer att ha kunskap om löftets status, eller tillgång att lösa eller avvisa.

här är en funktion som returnerar ett löfte som kommer att lösa efter en viss tidsfördröjning:

wait — promise exempel på CodePen

vårtwait(3000)samtal väntar 3000ms (3'Hello!'., Alla spec-kompatibla löften definierar en.then() – metod som du använder för att skicka hanterare som kan ta det upplösta eller avvisade värdet.

ES6 promise constructor tar en funktion. Den funktionen tar två parametrar, resolve()och reject(). I exemplet ovan använder vi bara resolve(), så jag lämnade reject() utanför parameterlistan. Då kallar vi setTimeout() för att skapa fördröjningen och kallar resolve() när den är klar.,

Du kan välja resolve() eller reject() med värden, som kommer att skickas till återuppringningsfunktionerna bifogade med .then().

När jagreject() med ett värde skickar jag alltid ettError – objekt. Generellt vill jag ha två möjliga resolutionsstater: den normala lyckliga vägen, eller ett undantag-allt som hindrar den normala lyckliga vägen från att hända. Att passera ettError – objekt gör det explicit.,

viktiga Löftesregler

en standard för löften definierades av Promises / a+ specification community. Det finns många implementeringar som överensstämmer med standarden, inklusive JavaScript standard ECMAScript-löften.

löften som följer spec måste följa en specifik uppsättning regler:

  • ett löfte eller ”thenable” är ett objekt som levererar en standard-kompatibel.then() metod.
  • ett väntande löfte kan övergå till ett uppfyllt eller avvisat tillstånd.,
  • ett uppfyllt eller avvisat löfte avgörs och får inte övergå till något annat tillstånd.
  • när ett löfte har lösts måste det ha ett värde (vilket kan vara undefined). Det värdet får inte ändras.

förändring i detta sammanhang avser identitet (===) jämförelse. Ett objekt kan användas som uppfyllt värde, och objektegenskaper kan mutera.,

varje löfte måste ge en.then() metod med följande signatur:

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

metoden.then() måste följa dessa regler:

  • bådeonFulfilled() ochonRejected() är valfria.
  • om argumenten inte är funktioner måste de ignoreras.
  • onFulfilled() kommer att ringas efter att löftet är uppfyllt, med löftets värde som det första argumentet.,
  • onRejected() kommer att ringas efter att löftet avvisas, med orsaken till avslag som det första argumentet. Anledningen kan vara något giltigt JavaScript-värde, men eftersom avslag i huvudsak är synonymt med undantag rekommenderar jag att du använder Felobjekt.
  • varkenonFulfilled() elleronRejected() kan anropas mer än en gång.
  • .then() kan anropas många gånger på samma löfte. Med andra ord kan ett löfte användas för att samla callbacks.,
  • .then() måste returnera ett nytt löfte,promise2.
  • Om onFulfilled() eller onRejected() returnerar ett värde x, och x är ett löfte, promise2 kommer att låsa in med (anta samma tillstånd och värde som) x. Annars kommerpromise2 att uppfyllas med värdetx.,
  • OmonFulfilled elleronRejected kastar ett undantage, måstepromise2 avvisas mede som orsak.
  • OmonFulfilledinte är en funktion ochpromise1är uppfyllt måstepromise2vara uppfyllt med samma värde sompromise1.,
  • OmonRejectedinte är en funktion ochpromise1avvisas måstepromise2avvisas av samma skäl sompromise1.

löfte kedja

eftersom.then() alltid returnerar ett nytt löfte, är det möjligt att kedja löften med exakt kontroll över hur och var fel hanteras. Löften kan du efterlikna normal synkron kodtry/catch beteende.,

som synkron kod kommer kedja att resultera i en sekvens som körs i seriell., Med andra ord kan du göra:

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

här är ett exempel på en komplex löfteskedja med flera avslag:

Promise chaining behavior example on CodePen

felhantering

Observera att löften har både en framgång och en felhanterare, och det är mycket vanligt att se kod som gör detta:

save().then(
handleSuccess,
handleError
);

men vad händer om handleSuccess() kastar ett fel?, Löftet som returneras från .then() kommer att avvisas, men det finns inget där för att fånga avslaget — vilket innebär att ett fel i din app blir svalt. Hoppsan!

av den anledningen anser vissa att koden ovan är ett anti-mönster och rekommenderar följande istället:

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

skillnaden är subtil men viktig. I det första exemplet kommer ett fel med ursprung i save() – åtgärden att fångas, men ett fel med ursprung ihandleSuccess() – funktionen kommer att sväljas.,

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., (diagram källa)

naturligtvis kan felet save() vara ett nätverksfel, medan felet handleSuccess() kan bero på att utvecklaren glömde att hantera en specifik statuskod. Vad händer om du vill hantera dem annorlunda? Du kan välja att hantera dem båda:

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

vad du än föredrar rekommenderar jag att du avslutar alla löfteskedjor med en .catch(). Det är värt att upprepa:

Jag rekommenderar att du slutar alla löfteskedjor med en .catch().,

hur avbryter jag ett löfte?

en av de första sakerna nya promise användare undrar ofta om är hur man avbryter ett löfte. Här är en idé: avvisa bara löftet med ”avbruten” som orsaken. Om du behöver hantera det annorlunda än ett ”normalt” fel, gör din förgrening i din felhanterare.

Här är några vanliga misstag människor gör när de rullar sitt eget löfte avbokning:

lägga till .,Avbryt() till löftet

lägga till .cancel() gör löftet icke-standard, men det bryter också mot en annan regel av löften: endast den funktion som skapar löftet ska kunna lösa, avvisa eller avbryta löftet. Att exponera det bryter inkapslingen och uppmuntrar människor att skriva kod som manipulerar löftet på platser som inte borde veta om det. Undvik spagetti och brutna löften.

glömmer att städa upp

några smarta människor har räknat ut att det finns ett sätt att använda Promise.race() som en avbokningsmekanism., Problemet med det är att avbokningskontroll tas från den funktion som skapar löftet, vilket är det enda stället som du kan utföra ordentlig sanering aktiviteter, såsom rensa timeouts eller frigöra minne genom att rensa referenser till data, etc…

glömmer att hantera ett avvisat Avbryt löfte

visste du att Chrome kastar varningsmeddelanden över hela konsolen när du glömmer att hantera ett löfte avslag? Hoppsan!

alltför komplex

det återkallade TC39-förslaget om avbokning föreslog en separat meddelandekanal för avbokningar., Det använde också ett nytt koncept som heter en avbokningstoken. Enligt min mening skulle lösningen ha kraftigt uppblåst löftesspecifikationen, och den enda funktionen som den skulle ha förutsatt att spekulationer inte direkt stöder är separationen av avslag och avbokningar, vilket IMO inte är nödvändigt att börja med.

vill du byta beroende på om det finns ett undantag eller en avbokning? Ja, absolut. Är det löftets jobb? Enligt min mening, nej, det är det inte.,

Rethinking Promise Cancellation

generellt skickar jag all information som löftet behöver för att bestämma hur man löser / avvisar / avbryter vid promise creation time. På så sätt finns det inget behov av en .cancel() metod på ett löfte. Du kanske undrar hur du eventuellt kan veta om du kommer att avbryta vid promise creation time.

”om jag ännu inte vet om jag ska avbryta, hur vet jag vad jag ska skicka in när jag skapar löftet?,”

om det bara fanns något slags objekt som kunde stå i för ett potentiellt värde i framtiden… Åh, vänta.

värdet vi skickar in för att representera huruvida att avbryta kan vara ett löfte i sig. Så här kan det se ut:

Cancellable wait — prova på CodePen

vi använder standardparametertilldelning för att berätta att den inte avbryts som standard. Det gör parameterncancel bekvämt valfri., Sedan sätter vi timeout som vi gjorde tidigare, men den här gången fångar vi timeout-ID så att vi kan rensa det senare.

vi använder metodencancel.then() för att hantera avbokning och resursrensning. Detta kommer bara att köras om löftet blir inställt innan det har en chans att lösa. Om du ställer in för sent, har du missat din chans. Tåget har lämnat stationen.

Obs! Du kanske undrar vad funktionennoop() är för. Ordet noop står för no-op, vilket betyder en funktion som inte gör någonting., Utan det kommer V8 att kasta varningar: UnhandledPromiseRejectionWarning: Unhandled promise rejection. Det är en bra idé att alltid hantera promise avslag, även om din handler är en noop().

Abstracting Promise Cancellation

detta är bra för enwait() timer, men vi kan abstrahera denna idé ytterligare för att kapsla allt du måste komma ihåg:

  1. avvisa Avbryt löfte som standard — vi vill inte avbryta eller kasta fel om ingen Avbryt löfte skickas in.
  2. kom ihåg att utföra rensning när du avvisar för avbokningar.,
  3. kom ihåg attonCancel rensning kan själv kasta ett fel, och det felet kommer också att behöva hanteras. (Observera att felhantering utelämnas i vänteexemplet ovan-Det är lätt att glömma!)

låt oss skapa ett avbokningsbart promise-verktyg som du kan använda för att pakka in något löfte., Till exempel, för att hantera nätverksförfrågningar, etc… kommer signaturen att se ut så här:

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

Specfunktionen är precis som den funktion du skulle passera in i konstruktören Promise, med ett undantag — det tar en onCancel() handler:

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

Observera att detta exempel bara är en illustration för att ge dig en översikt över hur det fungerar. Det finns några andra kantfall du måste ta hänsyn till., Till exempel, i den här versionen kommer handleCancel att ringas om du avbryter löftet efter att det redan är avgjort.

Jag har implementerat en underhållen produktionsversion av detta med kantfall som omfattas som open source-biblioteket, spekulation.

låt oss använda det förbättrade bibliotekets abstraktion för att skriva om det avbokningsbara wait() – verktyget från tidigare., Först installera spekulationer:

npm install --save speculation

Nu kan du importera och använda den:

detta förenklar saker lite, eftersom du inte behöver oroa dig för noop(), fånga fel i onCancel(), funktion eller andra kantfall. Dessa detaljer har abstraherats bort av speculation(). Kolla in det och gärna använda den i verkliga projekt.,

Extras av det ursprungliga JS-löftet

det ursprungligaPromise – objektet har några extra saker du kanske är intresserad av:

  • Promise.reject() returnerar ett avvisat löfte.
  • Promise.resolve() returnerar ett löst löfte.
  • Promise.race() tar en array (eller någon iterable) och returnerar ett löfte som löser med värdet av det första lösta löftet i det iterable, eller avvisar med anledning av det första löftet som avvisar.,
  • Promise.all() tar en array (eller någon iterable) och returnerar ett löfte som löser när alla löften i det iterable argumentet har lösts, eller avvisar med anledning av det första godkända löftet som avvisar.

slutsats

löften har blivit en integrerad del av flera idiom i JavaScript, inklusive WHATWG hämta standard som används för de flesta moderna Ajax förfrågningar, och async funktioner standard används för att göra asynkron kod ser synkron.,

async funktioner är steg 3 vid tidpunkten för detta skrivande, men jag förutspår att de snart kommer att bli en mycket populär, mycket vanlig lösning för asynkron programmering i JavaScript – vilket innebär att lära sig att uppskatta löften kommer att bli ännu viktigare för JavaScript-utvecklare inom en snar framtid.

till exempel, om du använder Redux, föreslår jag att du checkar ut redux-saga: ett bibliotek som används för att hantera biverkningar i Redux som beror på async-funktioner i hela dokumentationen.,

Jag hoppas att även erfarna promise-användare har en bättre förståelse för vad löften är och hur de fungerar, och hur man använder dem bättre efter att ha läst detta.

utforska serien

  • Vad är en stängning?
  • Vad är skillnaden mellan klass och prototypisk arv?
  • Vad är en ren funktion?
  • Vad är Funktionssammansättning?
  • Vad är funktionell programmering?
  • Vad är ett löfte?
  • mjuka färdigheter

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *