From 61d4f2a88925556f817733f53450840c137a3507 Mon Sep 17 00:00:00 2001 From: Sergey Slipchenko Date: Thu, 8 Feb 2024 07:46:01 +0400 Subject: [PATCH] [WIP] add AsyncResult --- src/asyncResult.ts | 89 ++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 1 + 2 files changed, 90 insertions(+) create mode 100644 src/asyncResult.ts diff --git a/src/asyncResult.ts b/src/asyncResult.ts new file mode 100644 index 0000000..011c8e7 --- /dev/null +++ b/src/asyncResult.ts @@ -0,0 +1,89 @@ +import { Async } from './async'; +import { Result } from './result'; + +/** + * A wrapper around Async> to help avoid nesting in a common + * scenario. + */ +export class AsyncResult { + #state; + + private constructor(state: Async>) { + this.#state = state; + } + + /** + * Represents a pending value + */ + static Pending = new AsyncResult(Async.Pending); + + /** + * Create a box representing failure + */ + static Err(err: E) { + return new AsyncResult(Async.Ready(Result.Err(err))); + } + + /** + * Create a box representing success + */ + static Ok(value: T) { + return new AsyncResult(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>>>( + xs: T, + ): AsyncResult< + { [K in keyof T]: T[K] extends AsyncResult ? U : never }, + { + [K in keyof T]: T[K] extends AsyncResult ? 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(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(f: (value: T) => U) { + return this.match>({ + Pending: () => AsyncResult.Pending, + Err: err => AsyncResult.Err(err), + Ok: value => AsyncResult.Ok(f(value)), + }); + } +} diff --git a/src/index.ts b/src/index.ts index f9fbf9c..b46f539 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ export { Async } from './async'; +export { AsyncResult } from './asyncResult'; export { Maybe } from './maybe'; export { Result } from './result';