Option
Rust-like Option for TypeScript
npm i @hazae41/option
Node Package 📦
Features
Current features
- 100% TypeScript and ESM
- No external dependencies
- Similar to Rust
unwrap()
for throwingunwrapOr()
for default valuemap()
for mapping (sync/async)isSome()
/isNone()
type guardsok()
/okOr()
for converting to Result from @hazae41/result
Why
TLDR undefined
is too low level and often leads to ugly and repetitive design patterns or bugs
When designing a function, you often encounter the case where you can't pass undefined
.
function doSomething(text: string) {
return Buffer.from(text, "utf8").toString("base64")
}
function bigFunction(text?: string) {
doSomething(text)
}
So you end up checking for undefined
Checking in the caller
function bigFunction(text?: string) {
if (text !== undefined) {
doSomething(text)
}
}
This is annoying if we want to get the returned value
function bigFunction(text?: string) {
if (text !== undefined) {
const text2 = doSomething(text)
}
}
Checks become redundant if you need to map the value or throw an error
function bigFunction(text?: string) {
const text2 = text === undefined
? undefined
: doSomething(text)
const text3 = text2 === undefined
? undefined
: doSomethingElse(text2)
if (text3 === undefined)
throw new Error(`something is wrong`)
}
Checking in the callee
Why not check for undefined
in the callee then?
function maybeDoSomething(text?: string) {
if (text === undefined) return
return Buffer.from(text, "utf8").toString("base64")
}
If you know your argument is NOT undefined
, it will force you to check for undefined
after the call
function bigFunction(text: string) {
const text2 = doSomething(text)
}
Or even worse, force you to use type assertion
function bigFunction(text: string) {
const text2 = doSomething(text) as string
}
Checking in an intermediary
Let's keep the original function and create an intermediary function for undefined
function maybeDoSomething(text?: string) {
if (text === undefined) return
return doSomething(text)
}
Now you have twice the amount of function in your app, and if one of them changes you have to change its intermediary function too
Using Option
function bigFunction(text?: string) {
const maybeText = Option.from(text)
const maybeText2 = maybeText.mapSync(doSomething)
const maybeText3 = maybeText2.mapSync(doSomethingElse)
const text4 = maybeText3.unwrap()
const text4 = maybeText3.okOr(new Error(`Something is wrong`)).unwrap()
const text4 = maybeText3.ok()
const text4 = maybeText3.okOr(new Error(`Something is wrong`))
const text4 = maybeText3.inner
if (maybeText3.isSome())
const text4 = maybeText3.inner
else
}