En aspekt av JavaScript-utveckling som många utvecklare kämpar med är att hantera valfria värden. Vilka är de bästa strategierna för att minimera fel orsakade av värden som kan vara null
, undefined
eller på annat sätt oinitialiseras vid körning?
Vissa språk har inbyggda överkomster för dessa omständigheter. På vissa statiskt skrivna språk kan du säga att null
och undefined
är olagliga värden och låta ditt programmeringsspråk kasta en TypeError vid sammanställningstid. , men även på dessa språk kan det inte förhindra att ingångar från null flyter in i programmet vid körning.
För att få ett bättre grepp om detta problem måste vi förstå var dessa värden kan komma ifrån. Här är några av de vanligaste källorna:
- Användarinmatning
- Databas / nätverksposter
- Oinitialiserat tillstånd
- Funktioner som kan returnera ingenting
När du har att göra med användarinmatning är validering den första och bästa försvarslinjen. Jag litar ofta på schemavaliderare för att hjälpa till med det jobbet. Kolla till exempel reagera -jsonschema-form.
Hydrating Records from Input
Jag skickar alltid ingångar som jag får från nätverket, databasen eller användarinmatningen via en hydratiseringsfunktion. Till exempel använder jag redux action skapare som kan hantera
värden för att hydrera användarposter:
const setUser = ({ name = "Anonymous", avatar = "anon.png" } = {}) => ({
type: setUser.type,
payload: {
name,
avatar
}
});
setUser.type = "userReducer/setUser";
Ibland måste du visa olika saker beroende på det aktuella läget av uppgifterna. Om det är möjligt att visa en sida innan all information initialiseras kan du befinna dig i den situationen. När du till exempel visar saldon för en användare kan du av misstag visa ett saldo på $ 0 innan data laddas. Jag har sett detta upprörda användare flera gånger. Du kan skapa anpassade datatyper som genererar olika utdata baserat på det aktuella tillståndet:
Koden ovan är en tillståndsmaskin som gör det omöjligt att visa ogiltiga tillstånd. När du skapar saldot först ställs det in i ett uninitialized
-tillstånd. Om du försöker visa en balans medan tillståndet är uninitialized
får du alltid ett platshållarvärde (”--
”) istället.
För att ändra det måste du uttryckligen ange ett värde genom att anropa .set
-metoden eller setBalance
genväg definierade vi nedan createBalance
fabriken.
Själva staten är inkapslad för att skydda den från yttre störningar för att se till att andra funktioner inte kan fånga den och ställa in den till ogiltigt tillstånd.
Obs! Om du undrar varför vi använder strängar istället för siffror för det, beror det på att jag representerar pengar med stora talsträngar med mycket decimalprecision för att undvika avrundningsfel och korrekt representera värden för kryptovalutatransaktioner, som kan ha godtycklig betydande decimalprecision.
Om du ’ om du använder Redux eller Redux-arkitektur kan du deklarera tillståndsmaskiner med Redu x-DSM.
Undvik att skapa null och odefinierade värden
I dina egna funktioner kan du undvika att skapa null
eller undefined
värden till att börja med. Det finns ett par sätt att göra det inbyggt i JavaScript som kommer att tänka på. Se nedan.
Undvik null
Jag skapar aldrig uttryckligen null
värden i JavaScript, för jag såg egentligen inte poängen med att ha två olika primitiva värden som i huvudsak betyder ”det här värdet finns inte.”
Sedan 2015 har JavaScript stött standardvärden som fylls i när du inte anger ett värde för argumentet eller egenskapen i fråga. Dessa standardinställningar fungerar inte för null
-värden. Enligt min erfarenhet är det vanligtvis ett fel. För att undvika den fällan ska du inte använda null
i JavaScript.
Om du vill ha speciella fall för icke-initialiserade eller tomma värden är tillståndsmaskiner en bättre satsning. Se ovan.
Nya JavaScript-funktioner
Det finns ett par funktioner som kan hjälpa dig att hantera null
eller undefined
värden. Båda är steg 3-förslag när detta skrivs, men om du läser från framtiden kan du kanske använda dem.
När detta skrivs är valfri kedja ett steg 3-förslag. Det fungerar så här:
const foo = {};
// console.log(foo.bar.baz); // throws error
console.log(foo.bar?.baz) // undefined
Nullish Coalescing Operator
Också ett steg 3-förslag som ska läggas till specifikationen, ”nullish coalescing operator ”Är i grunden ett snyggt sätt att säga” reservvärdeoperatör ”. Om värdet till vänster är undefined
eller null
, utvärderas det till värdet till höger.Det fungerar så här:
Asynkron antingen med löften
Om en funktion kanske inte återkommer med ett värde kan det vara en bra idé att slå in den i endera antingen. I funktionell programmering är antingen monaden en speciell abstrakt datatyp som låter dig bifoga två olika kodvägar: en framgångsväg eller en misslyckande sökväg. JavaScript har en inbyggd asynkron antingen monadisk datatyp som heter Promise. Du kan använda den för att göra deklarativa felgrenar för odefinierade värden:
Du kan skriva en synkron version av det om du vill, men jag har inte behövt det mycket. Jag lämnar det som en övning för dig. Om du har en bra jordning i funktioner och monader blir processen enklare. Om det låter skrämmande, oroa dig inte för det. Använd bara löften. De är inbyggda och de fungerar bra för det mesta.
Arrays för Maybes
Arrays implementerar en map
-metod som tar en funktion som tillämpas på varje element i matrisen. Om matrisen är tom kommer funktionen aldrig att anropas. Med andra ord kan Arrays i JavaScript fylla Maybes roll från språk som Haskell.
Vad är en kanske?
A Kanske är en speciell abstrakt datatyp som inkapslar ett valfritt värde . Datatypen har två former:
- Just – A Kanske som innehåller ett värde
- Ingenting – en kanske utan värde
Här är idéens kärnpunkt:
Detta är bara ett exempel för att demonstrera konceptet. Du kan bygga ett helt bibliotek med användbara funktioner runt maybes och implementera andra åtgärder som flatMap
och flat
(t.ex. för att undvika Just(Just(value))
när du komponerar flera funktioner som kanske returnerar). Men JavaScript har redan en datatyp som implementerar många av dessa funktioner out-of-the-box, så jag brukar nå det istället: Array.
Om du vill skapa en funktion som kan eller kanske inte ger ett resultat (särskilt om det kan finnas fler än ett resultat), kan du ha ett bra användningsfall för att returnera en matris.
Jag tycker att map
kommer inte att anropas på en tom lista som är mycket användbar för att undvika null
och undefined
värden, men kom ihåg om matrisen innehåller null
och undefined
värden, det kommer att kalla funktionen med dessa värden, så om funktionen du kör kan producera null
eller undefined
, du måste filtrera bort dem från din returnerade array, vilket visas ovan. Det kan leda till att längden på samlingen.
I Haskell finns det en funktion maybe
som (som ) tillämpar en funktion på ett värde. Men värdet är valfritt och inkapslat i en Maybe
. Vi kan använda JavaScript ”s Array
datatyp för att göra i princip samma sak:
maybe
tar ett reservvärde , sedan en funktion för att mappa över kanske matrisen, sedan en kanske matris (en matris som innehåller ett värde eller ingenting), och returnerar antingen resultatet av att tillämpa funktionen på matrisens innehåll eller reservvärdet om matrisen är tom.
För bekvämlighets skull har jag också definierat en toMaybeArray
-funktion och curried funktionen maybe
det mest uppenbara för denna demonstration.
Om du vill göra något liknande i produktionskoden har jag skapat ett enhetstestat öppen källkodsbibliotek för att göra det lättare. Det heter Maybearray. Fördelen med Maybearray jämfört med andra JavaScript-bibliotek kanske är att den använder inbyggda JavaScript-arrays för att representera värden, så du behöver inte ge dem någon speciell behandling eller göra något speciellt för att konvertera fram och tillbaka. När du stöter på kanske arrays i din felsökning behöver du inte fråga ”vad är den här konstiga typen ?!” Det är bara en matris med ett värde eller en tom matris och du har sett dem en miljon gånger tidigare.
Nästa steg
Det finns mycket mer innehåll på EricElliottJS.com, inklusive massor med videor, övningar, inspelade screencasts och snabba tips. Om du inte är medlem är det här en bra tid att se vad du saknat!