Skip to content

Commit

Permalink
[WIP] add AsyncResult
Browse files Browse the repository at this point in the history
  • Loading branch information
faergeek committed Feb 8, 2024
1 parent 6e4d926 commit 61d4f2a
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 0 deletions.
89 changes: 89 additions & 0 deletions src/asyncResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { Async } from './async';
import { Result } from './result';

/**
* A wrapper around Async<Result<T, E>> to help avoid nesting in a common
* scenario.
*/
export class AsyncResult<T, E> {
#state;

private constructor(state: Async<Result<T, E>>) {
this.#state = state;
}

/**
* Represents a pending value
*/
static Pending = new AsyncResult<never, never>(Async.Pending);

/**
* Create a box representing failure
*/
static Err<const E>(err: E) {
return new AsyncResult<never, E>(Async.Ready(Result.Err(err)));
}

/**
* Create a box representing success
*/
static Ok<T>(value: T) {
return new AsyncResult<T, never>(Async.Ready(Result.Ok(value)));
}

/**
* Combine record of boxes and return either record with their unboxed values
* if all boxes represent a success or the first box representing failure
* otherwise
*/
static all<T extends Readonly<Record<string, AsyncResult<unknown, unknown>>>>(
xs: T,
): AsyncResult<
{ [K in keyof T]: T[K] extends AsyncResult<infer U, unknown> ? U : never },
{
[K in keyof T]: T[K] extends AsyncResult<unknown, infer F> ? F : never;
}[keyof T]
> {
return new AsyncResult(
Async.all(
Object.fromEntries(Object.entries(xs).map(([k, v]) => [k, v.#state])),
).mapReady(Result.all),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any;
}

/**
* Unpack the box. Requires explicit handling of pending, success and failure
* states.
* This is used to implement most of the operations
*/
match<U, F = U, P = U>(arms: {
Ok: (value: T) => U;
Err: (value: E) => F;
Pending: () => P;
}) {
return this.#state.match({
Pending: arms.Pending,
Ready: result =>
result.match({
Err: arms.Err,
Ok: arms.Ok,
}),
});
}

/**
* Apply an arbitrary transformation to a success value inside the box and
* return a new box containing the result of that transformation.
* `f` accepts an existing value. Returned value will be put inside the new
* box representing success. Not called if a box represents either a failure
* or a pending state
*/
mapOk<U>(f: (value: T) => U) {
return this.match<AsyncResult<U, E>>({
Pending: () => AsyncResult.Pending,
Err: err => AsyncResult.Err(err),
Ok: value => AsyncResult.Ok(f(value)),
});
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { Async } from './async';
export { AsyncResult } from './asyncResult';
export { Maybe } from './maybe';
export { Result } from './result';

0 comments on commit 61d4f2a

Please sign in to comment.