
Funksjonell programmering i Enonic XP #3 - Option
I stedet for å returnere null-verdier kan vi returnere en Option, som tvinger utvikleren til å håndtere tilfellet der verdien mangler.
Håndtere null
Den neste sideeffekten vi må håndtere er at contentLib.get
kan returnere null
, dersom verdien ikke eksisterer.
TypeScript-kompilatoren tvinger utvikleren til å ta høyde for null-verdier, så problemet med "runtime errors" når en verdi enten er null
eller undefined
, er mindre i TypeScript enn i JavaScript.
I funksjonell programmerings verden har vi et eget konsept for å håndtere manglende verdier, og det er Option<A>
. En Option<A> kan være én av to typer. Enten en Some<A>
, som betyr at det finnes en verdi. Eller en None
, som betyr at det ikke finnes en verdi.
Eksempel:
Vi kan pakke inn resultatet fra en content.get()
i en Option
, for å representere muligheten for at verdien for den nøkkelen ikke finnes i databasen.
I dette eksempelet lager vi en funksjon getEmployee()
, som returnerer Option<Content<Employee>>
. Det er også en funksjon get
, som bruker en fold()
for å håndtere resultatet av getEmployee()
for å lage en Response
.
Det er en god tommelfingerregel at man kun lager Response
i get
, post
etc. funksjoner i controllere. Og den vanligste måten å gjøre det er å gjøre en fold()
over noe data man har hentet fra databasen.
Merk deg hvordan utvikleren her blir tvunget til å håndtere null-tilfellet, når de vil "pakke ut" verdien de får returnert som en Option
. Funksjonell programmering tvinger oss til å håndtere det som ikke er "happy-pathen", og det er en veldig bra ting.
import { fold, none, Option, some } from 'fp-ts/Option';
import { Employee } from '../../content-types/employee/employee'
import { Content, ContentLibrary } from "enonic-types/content";
import { Request, Response } from "enonic-types/controller";
const contentLib: ContentLibrary = __non_webpack_require__('/lib/content');
/**
* Returns employee content, if its found
*/
function getEmployee(key: string): Option<Content<Employee>> {
const employee = contentLib.get<Employee>({ key });
return (employee !== null)
? some(employee)
: none;
}
/**
* Controllers get function
*/
export function get(req: Request): Response {
const optionEmployee = getEmployee(req.params.key!);
return fold(
() => (
{
status: 404
}
),
(employeeContent) => (
{
status: 200,
body: employeeContent
}
)
)(optionEmployee);
}
Hjelpefunksjoner
Eksempel:
Det fins også en hjelpefunksjon som man kan bruke for å gjøre en "nullable" verdi om til en Option
, og det er fromNullable()
.
Du kan se at getEmployee()
i det neste eksempel fungerer på akkurat samme måte som den gjorde i forrige eksempel, men med litt enklere kode.
import { fromNullable, Option } from 'fp-ts/lib/Option';
import { Employee } from '../../content-types/employee/employee'
import { Content, ContentLibrary } from "enonic-types/lib/content";
const contentLib: ContentLibrary = __non_webpack_require__('/lib/content');
/**
* Returns employee content, if its found
*/
function getEmployee(key: string): Option<Content<Employee>> {
return fromNullable(contentLib.get<Employee>({ key }));
}
// ...resten av koden er det samme som over
Veien videre
I neste kapittel skal vi se på håndtering av Exceptions med Either<E, A>
.