Skip to content

Instantly share code, notes, and snippets.

@romgrk
Created April 11, 2023 14:32
Show Gist options
  • Save romgrk/4466f7043774850ada532904a77bedf1 to your computer and use it in GitHub Desktop.
Save romgrk/4466f7043774850ada532904a77bedf1 to your computer and use it in GitHub Desktop.
import Result from './Result'
import { Loader } from './useLoader'
enum State {
UNSET,
LOADING,
/** Or "LOADED" */
OK,
ERROR,
}
export default class AsyncResult<T> {
state: State
value: T | undefined
error: Error | undefined
static Unset() { return new Unset() }
static Loading() { return new Loading() }
static Ok<T>(data: T) { return new Ok<T>(data) }
static Err(error: Error) { return new Err(error) }
static from<S>(result: Result<S>) {
return result.ok ?
AsyncResult.Ok(result.unwrap()) :
AsyncResult.Err(result.unwrapErr())
}
static fromOptional<S>(value?: S): AsyncResult<S> {
return value !== undefined ?
AsyncResult.Ok(value) :
AsyncResult.Unset()
}
static fromLoader<S>(loader: Loader<S>): AsyncResult<S> {
if (loader.data.isSome())
return AsyncResult.Ok(loader.data.unwrap())
if (loader.isLoading)
return AsyncResult.Loading()
if (loader.error)
return AsyncResult.Err(loader.error)
return AsyncResult.Unset()
}
/* eslint-disable */
static load<S>(setter: (r: AsyncResult<S>) => unknown, promise: Promise<Result<S>>): Promise<AsyncResult<S>>;
static load<S>(setter: (r: AsyncResult<S>) => unknown, promise: Promise<unknown>): Promise<AsyncResult<S>> {
/* eslint-enable */
setter(AsyncResult.Loading())
return promise
.then(result => {
if (result instanceof Result)
return AsyncResult.from(result)
else
return AsyncResult.Ok(result)
})
.catch(error => {
return AsyncResult.Err(error)
})
.then(result => {
setter(result)
return result
})
}
constructor(state: State, value?: T, error?: Error) {
this.state = state
this.value = value
this.error = error
}
unwrap(): T {
if (this.state !== State.OK)
throw new Error('Trying to unwrap an unloaded async result')
return (this as unknown as Ok<T>).value as any
}
unwrapOrElse<S>(value: S): T | S {
if (this.state !== State.OK)
return value
return this.value as any
}
unwrapErr() {
if (this.state !== State.ERROR)
throw new Error('Trying to unwrap unexistent error')
return (this as unknown as Err).error
}
map<S>(fn: (m: T) => S): AsyncResult<S> {
if (this.state !== State.OK)
return (this as unknown as AsyncResult<S>)
return AsyncResult.Ok(fn(this.value as any))
}
pick<S extends keyof T>(prop: S): AsyncResult<T[S]> {
if (this.state !== State.OK)
return (this as unknown as AsyncResult<T[S]>)
return AsyncResult.Ok(this.value![prop])
}
flatMap<S>(fn: (m: T) => AsyncResult<S>): AsyncResult<S> {
if (this.state !== State.OK)
return (this as unknown as AsyncResult<S>)
return fn(this.value as any)
}
isUnset(): this is Unset {
return this.state === State.UNSET
}
isLoading(): this is Loading {
return this.state === State.LOADING
}
isOk(): this is Ok<T> {
return this.state === State.OK
}
isErr(): this is Err {
return this.state === State.ERROR
}
fold<S>(
unsetFn: (() => S) | undefined,
loadingFn: () => S,
errorFn: (e: Error) => S,
loadedFn: (v: T) => S
): S {
switch (this.state) {
case State.UNSET: return (unsetFn ?? loadingFn)()
case State.LOADING: return loadingFn()
case State.ERROR: return errorFn(this.error as Error)
case State.OK: return loadedFn(this.value as T)
}
}
}
class Unset extends AsyncResult<any> {
declare value: undefined
declare error: undefined
constructor() {
super(State.UNSET, undefined, undefined)
}
}
class Loading extends AsyncResult<any> {
declare value: undefined
declare error: undefined
constructor() {
super(State.LOADING, undefined, undefined)
}
}
class Ok<T> extends AsyncResult<T> {
declare value: T
declare error: undefined
constructor(value: T) {
super(State.OK, value, undefined)
}
}
class Err extends AsyncResult<any> {
declare value: undefined
declare error: Error
constructor(error: Error) {
super(State.ERROR, undefined, error)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment