@standardize/optional
v0.0.1
Published
AKA _Optional(Java/OOP)_ _Option(Scala/OCAML)_ _Maybe(Haskell)_
Downloads
1
Readme
@standardize/optional
AKA Optional(Java/OOP) Option(Scala/OCAML) Maybe(Haskell)
API Docs
Why is this different from other libraries?
Javascripts primary strength is its simple asynchronous code. This optional
implementation is able to map into and out of the underlying AsyncOptional
and Optional
interfaces without the user needing to keep track of the async
state of execution.
Other libraries like async-optional (also no TS definitions) require you to define that the optional will be async up front instead of being able to map into an async version at will.
Why?
Applying functions to potentially null/missing values has been the bane of many programs. The optional helps refactor potentially dangerous or verbose code into a check free style.
import { Optional } from 'designed'
import { storeUser, hashPassword, updateTimestamps, emptyUser, findUser } from './UserRepo'
function updateUserPassword(userId?: string, password: string) {
await Optional.of(userId)
.mapAsync(findUser)
.map(addHashedPasswordToUser(password)) // Perform a sync transformation
.map(updateTimestamps) // Add a value
.mapAsync(storeUser) // Store the user asynchronously
.orElse(emptyUser())
}
updateUserPassword(undefined, '123') // Resolves just fine
updateUserPassword(userId, '123') // Also resolves!
Public Interface
isPresent
/ isAbsent
Acts as a type guard to check whether the value is present. This should always
be used before issuing Optional.get()
to prevent throwing exceptions.
if (maybeUser.isPresent()) {
maybeUser; // PresentOptional<User>
}
filter
/ filterNot
If the predicate is true the value will remain a PresentOptional
.
Optional.of("ABC").filter((s) => s.length > 3); // AbsentOptional<'ABC'>
Optional.of("ABC").filterNot((s) => s.length > 3); // PresentOptional<'ABC'>
Type narrowing is supported by supplying a User Defined Type Guard as the predicate.
const optional =
Optional.of<string | number>(123) // Optional<string | number>
const isString =
(v: Object): v is string => typeof v === 'string'
optional.filter(isString) // Optional<string>
optional.filterNot(isString) // Optional<number>
map
Applies a function to the wrapped value if it is present. If the return value
is absent (null
or undefined
) it will return an AbsentOptional
Optional.of("ABC")
.map((s) => s.toLowerCase())
.get(); // 'abc'
Optional.of < string > null.map((s) => s.toLowerCase()).orElse("not there"); // 'not there'
flatMap
Applies a function to the wrapped value that returns another Optional
const readFileDataAtPathIfExists = (filepath: string): Optional<Buffer> => {
/* implement */
};
Optional.of("./realfile.md").map(readFileDataAtPathIfExists); // Optional<Optional<Buffer>>
Optional.of("./realfile.md").flatMap(readFileDataAtPathIfExists); // Optional<Buffer>
Optional.of("not a file").flatMap(readFileDataAtPathIfExists); // Optional<Buffer>
toResult
Converts the Optional<T>
to a Result<T, null>
Zip Methods
404 Documentation not complete
Unwrapping Methods
orElse
Return the wrapped value or "else" the value passed to the function.
Optional.of("a string").orElse(123) // Typed: string | number Result: "a string"
Optional.empty().orElse(123) // Result: 123
orGet
Invokes the passed method and returns its value if the optional value is absent.
Optional.of("a string").orElse(() => 123) // Typed: string | number Result: "a string"
This is particularly useful in async methods. As any value can have await
applied to it, you can easily setup fallback behaviour without requiring branching.
await Optional.of(userId).mapAsync(findUserAsync).orElse(createUserAsync); // Will always return an `User`. createUserAsync is only invoked if the previous value is empty
orThrow
If the value is missing, throws the error supplied by the passed function.
If an error is not returned from orThrow
a TypeError
will be thrown instead.
Optional.empty().orThrow(() => new Error("was empty")); // throws Error
Optional.of(1).orThrow(() => new Error("was empty")); // 1
Async Methods
Optionals can traverse back and forth between being in an async and sync state.
AsyncOptional
is a PromiseLike
class that can be await
'ed to make your
optionals easier to use with async code.
All methods available on sync Optional
's are also available when the optional is async.
mapAsync
Performs a similar function to the above but returns an AsyncOptional
depending on whether the value existed. This object is await
'able and is used
instead of a promise.
const fetchUserFromAPIById = (id: string) => fetch(/* details */);
await Optional.of("123").mapAsync(fetchUserFromAPIById).orElse("notThere"); // Fetch response
await Optional.empty().mapAsync(fetchUserFromAPIById).orElse("notThere"); // "notThere"
flatMapAsync
Performs a similar function to mapAsync
and flatMap
. It expects a function
that returns a Promise<Optional<T>>
and will flat map the Optional<T>
into
itself.
const searchForBlogPostByTitle = async (
title: string
): Promise<Optional<BlogPost>> => {
/* implement */
};
await Optional.of("Big Software").flatMapAsync(searchForBlogPostByTitle); // Resolves to Optional<BlogPost>
await Optional.empty().flatMapAsync(searchForBlogPostByTitle); // Also resolves to Optional<BlogPost>