Skip to content

Instantly share code, notes, and snippets.

@timruffles
Last active April 23, 2021 17:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timruffles/4aa264059265b412a9bebd007dfaa0a0 to your computer and use it in GitHub Desktop.
Save timruffles/4aa264059265b412a9bebd007dfaa0a0 to your computer and use it in GitHub Desktop.
operation - like Result and various other ADTs in Haskell etc
export type Operation<T, E> =
typeof NotStarted |
typeof Waiting |
Complete<T> |
Failed<E>
export enum OperationState {
NotStartedState = 'NotStarted',
WaitingState = 'Waiting',
CompleteState = 'Complete',
FailedState = 'Failed',
}
export const NotStarted: {
readonly opState: OperationState.NotStartedState
} = {
opState: OperationState.NotStartedState
}
export const Waiting: {
readonly opState: OperationState.WaitingState
} = {
opState: OperationState.WaitingState
}
export class Complete<T> {
readonly opState = OperationState.CompleteState
constructor(
readonly value: T
) {
}
}
export class Failed<E> {
readonly opState = OperationState.FailedState
constructor(
readonly error: E
) {
}
static fromCatch(e: Error | any): Failed<{ name: string, message: string }> {
if (e instanceof Error) {
const {name, message} = e
return new Failed({name, message})
}
return new Failed({
name: 'Unknown',
message: "An unknown error occurred"
})
}
static fromError(e: Error): Failed<{ name: string, message: string }> {
const {name, message} = e
return new Failed({name, message})
}
}
export type Handler<T, E, R> = {
notStarted: () => R,
waiting: () => R,
complete: (t: T) => R,
failed: (e: E) => R,
}
type BooleanHandler<T, R> = {
present: (t: T) => R,
absent: () => R,
}
export function isWaiting<T, E>(op: Operation<T, E>): op is typeof Waiting {
return op.opState === OperationState.WaitingState
}
export function isComplete<T, E>(op: Operation<T, E>): op is Complete<T> {
return op.opState === OperationState.CompleteState
}
export function isFailed<E>(op: Operation<any, E>): op is Failed<E> {
return op.opState === OperationState.FailedState
}
export function handle<T, E, R>(op: Operation<T, E>, handler: Handler<T, E, R>): R {
switch (op.opState) {
case OperationState.CompleteState:
return handler.complete(op.value)
case OperationState.FailedState:
return handler.failed(op.error)
case OperationState.NotStartedState:
return handler.notStarted()
case OperationState.WaitingState:
return handler.waiting()
}
}
// transform both value containing cases of the operation - useful for turning a complex op type into a displayable one
export const mapOperation = <T, U, E, F>(op: Operation<T, E>, mapComplete: (t:T) => U, mapFailed: (e:E) => F): Operation<U,F> => (
op.opState === OperationState.CompleteState
? new Complete(mapComplete(op.value))
: op.opState === OperationState.FailedState
? new Failed(mapFailed(op.error))
: op
)
export const handlePresence = <T, E, R>(op: Operation<T, E>, handler: BooleanHandler<T, R>): R => (
op.opState === OperationState.CompleteState
? handler.present(op.value)
: handler.absent()
)
export const mapComplete = <T, E, U>(op: Operation<T, E>, fn: (t: T) => U): Operation<U, E> => (
op.opState === OperationState.CompleteState
? new Complete(fn(op.value))
: op
)
export const mapCompleteAndGet = <T, E, U>(op: Operation<T, E>, fn: (t: T) => U, getAbsent: () => U): U => (
op.opState === OperationState.CompleteState
? fn(op.value)
: getAbsent()
)
export const mapAndGetPresence = <T, E, R>(op: Operation<T, E>, handler: BooleanHandler<T, R>): R => (
op.opState === OperationState.CompleteState
? handler.present(op.value)
: handler.absent()
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment