273 lines
8.1 KiB
TypeScript
273 lines
8.1 KiB
TypeScript
import {
|
|
Err,
|
|
FlattenResult,
|
|
Ok,
|
|
type Result,
|
|
type UnwrapOption,
|
|
} from "@shared/utils/result.ts";
|
|
import { None, none, Option, Some, some } from "@shared/utils/option.ts";
|
|
|
|
export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
|
|
constructor(private readonly _promise: Promise<Result<T, E>>) {
|
|
this._promise = _promise;
|
|
}
|
|
|
|
static fromPromise<T, E>(
|
|
promise: Promise<T>,
|
|
errorMapper: (error: unknown) => E,
|
|
) {
|
|
const promiseOfResult: Promise<Result<T, E>> = promise
|
|
.then((value: T): Result<T, E> => {
|
|
return new Ok<T, E>(value);
|
|
})
|
|
.catch((error: unknown): Result<T, E> => {
|
|
return new Err<T, E>(errorMapper(error));
|
|
});
|
|
|
|
return new ResultAsync<T, E>(promiseOfResult);
|
|
}
|
|
|
|
static fromSafePromise<T>(promise: Promise<T>) {
|
|
const promiseOfResult: Promise<Result<T, never>> = promise.then(
|
|
(value: T): Result<T, never> => {
|
|
return new Ok<T, never>(value);
|
|
},
|
|
);
|
|
|
|
return new ResultAsync<T, never>(promiseOfResult);
|
|
}
|
|
|
|
static fromThrowable<
|
|
Fn extends (...args: readonly any[]) => Promise<any>,
|
|
Em extends ErrorMapper<any>,
|
|
>(
|
|
fn: Fn,
|
|
errorMapper?: Em,
|
|
): (
|
|
...args: Parameters<Fn>
|
|
) => ResultAsync<
|
|
UnwrapPromise<ReturnType<Fn>>,
|
|
ExtractErrorFromMapper<Em>
|
|
> {
|
|
return (
|
|
...args: Parameters<Fn>
|
|
): ResultAsync<
|
|
UnwrapPromise<ReturnType<Fn>>,
|
|
ExtractErrorFromMapper<Em>
|
|
> => {
|
|
return ResultAsync.fromPromise(
|
|
fn(args),
|
|
(e) => errorMapper ? errorMapper(e) : e,
|
|
);
|
|
};
|
|
}
|
|
|
|
async unwrap(): Promise<T> {
|
|
const result = await this._promise;
|
|
if (result.isErr()) {
|
|
throw result.error;
|
|
}
|
|
|
|
return result.value;
|
|
}
|
|
|
|
async match<A, B = A>(
|
|
ok: (value: T) => A,
|
|
err: (err: E) => B,
|
|
): Promise<A | B> {
|
|
const result = await this._promise;
|
|
|
|
if (result.isErr()) {
|
|
return err(result.error);
|
|
}
|
|
return ok(result.value);
|
|
}
|
|
|
|
map<U>(fn: (value: T) => U): ResultAsync<U, E> {
|
|
return new ResultAsync(
|
|
this._promise.then((result: Result<T, E>): Result<U, E> => {
|
|
if (result.isErr()) {
|
|
return new Err<U, E>(result.error);
|
|
}
|
|
|
|
return new Ok<U, E>(fn(result.value));
|
|
}),
|
|
);
|
|
}
|
|
|
|
mapAsync<U>(fn: (value: T) => Promise<U>): ResultAsync<U, E> {
|
|
return new ResultAsync(
|
|
this._promise.then(
|
|
async (result: Result<T, E>): Promise<Result<U, E>> => {
|
|
if (result.isErr()) {
|
|
return errAsync<U, E>(result.error);
|
|
}
|
|
|
|
return new Ok<U, E>(await fn(result.value));
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
mapErr<U>(fn: (err: E) => U): ResultAsync<T, U> {
|
|
return new ResultAsync(
|
|
this._promise.then((result: Result<T, E>): Result<T, U> => {
|
|
if (result.isErr()) {
|
|
return new Err<T, U>(fn(result.error));
|
|
}
|
|
|
|
return new Ok<T, U>(result.value);
|
|
}),
|
|
);
|
|
}
|
|
|
|
mapErrAsync<U>(fn: (value: T) => Promise<U>): ResultAsync<T, U> {
|
|
return new ResultAsync(
|
|
this._promise.then(
|
|
async (result: Result<T, E>): Promise<Result<T, U>> => {
|
|
if (result.isErr()) {
|
|
return errAsync<T, U>(await fn(result.error));
|
|
}
|
|
|
|
return new Ok<T, U>(result.value);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
andThen<U, F>(fn: (value: T) => Result<U, F>): ResultAsync<U, E | F> {
|
|
return new ResultAsync(
|
|
this._promise.then(
|
|
(result: Result<T, E>): ResultAsync<U, E | F> => {
|
|
if (result.isErr()) {
|
|
return errAsync(result.error);
|
|
}
|
|
|
|
return fn(result.value).toAsync() as ResultAsync<U, E | F>;
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
andThenAsync<U, F>(
|
|
fn: (value: T) => ResultAsync<U, E | F> | Promise<Result<U, E | F>>,
|
|
): ResultAsync<U, E | F> {
|
|
return new ResultAsync(
|
|
this._promise.then(
|
|
(result: Result<T, E>): ResultAsync<U, E | F> => {
|
|
if (result.isErr()) {
|
|
return errAsync(result.error);
|
|
}
|
|
|
|
return fn(result.value) as ResultAsync<U, E | F>;
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
nullableToOption(): ResultAsync<Option<NonNullable<T>>, E> {
|
|
return this.map((v) => (v ? some(v) : none));
|
|
}
|
|
|
|
flatten(): FlattenResultAsync<ResultAsync<T, E>> {
|
|
return new ResultAsync(
|
|
this._promise.then(
|
|
(result: Result<T, E>): FlattenResult<Result<T, E>> => {
|
|
return result.flatten();
|
|
},
|
|
),
|
|
) as FlattenResultAsync<ResultAsync<T, E>>;
|
|
}
|
|
|
|
flattenOption<U = never>(
|
|
errFn: () => U,
|
|
): ResultAsync<UnwrapOption<T>, E | U> {
|
|
return new ResultAsync(
|
|
this._promise.then(
|
|
(result: Result<T, E>): Result<UnwrapOption<T>, E | U> => {
|
|
return result.flattenOption(errFn);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
flattenOptionOrDefault<D = UnwrapOption<T>>(
|
|
defaultValue: D,
|
|
): ResultAsync<UnwrapOption<T> | D, E> {
|
|
return new ResultAsync(
|
|
this._promise.then(
|
|
(result: Result<T, E>): Result<UnwrapOption<T> | D, E> => {
|
|
return result.flattenOptionOrDefault(defaultValue);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
matchOption<A, B = A>(
|
|
some: (value: UnwrapOption<T>) => A,
|
|
none: () => B,
|
|
): ResultAsync<A | B, E> {
|
|
return new ResultAsync(
|
|
this._promise.then((result: Result<T, E>): Result<A | B, E> => {
|
|
return result.matchOption(some, none);
|
|
}),
|
|
);
|
|
}
|
|
|
|
matchOptionAndFlatten<A, B, U, F>(
|
|
some: (value: UnwrapOption<T>) => Result<A, U>,
|
|
none: () => Result<B, F>,
|
|
): ResultAsync<A | B, E | U | F> {
|
|
return new ResultAsync(
|
|
this._promise.then(
|
|
(result: Result<T, E>): Result<A | B, E | U | F> => {
|
|
return result.matchOptionAndFlatten(some, none);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
then<A, B>(
|
|
onFulfilled?: (res: Result<T, E>) => A | PromiseLike<A>,
|
|
onRejected?: (reason: unknown) => B | PromiseLike<B>,
|
|
): PromiseLike<A | B> {
|
|
return this._promise.then(onFulfilled, onRejected);
|
|
}
|
|
}
|
|
|
|
export function okAsync<T, E = never>(value: T): ResultAsync<T, E>;
|
|
export function okAsync<T extends void = void, E = never>(
|
|
value: void,
|
|
): ResultAsync<void, E>;
|
|
export function okAsync<T, E = never>(value: T): ResultAsync<T, E> {
|
|
return new ResultAsync(Promise.resolve(new Ok<T, E>(value)));
|
|
}
|
|
|
|
export function errAsync<T = never, E extends string = string>(
|
|
err: E,
|
|
): ResultAsync<T, E>;
|
|
export function errAsync<T = never, E = unknown>(err: E): ResultAsync<T, E>;
|
|
export function errAsync<T = never, E extends void = void>(
|
|
err: void,
|
|
): ResultAsync<T, void>;
|
|
export function errAsync<E, T = never>(err: E): ResultAsync<T, E> {
|
|
return new ResultAsync(Promise.resolve(new Err<T, E>(err)));
|
|
}
|
|
|
|
export type FlattenResultAsync<R> = R extends ResultAsync<infer T, infer E>
|
|
? T extends ResultAsync<any, any>
|
|
? FlattenResultAsync<T> extends ResultAsync<infer V, infer innerE>
|
|
? ResultAsync<V, E | innerE>
|
|
: never
|
|
: R
|
|
: never;
|
|
|
|
type UnwrapPromise<Pr extends Promise<unknown>> = Pr extends Promise<infer U>
|
|
? U
|
|
: never;
|
|
|
|
type ErrorMapper<E> = ((e: unknown) => E) | undefined;
|
|
|
|
type ExtractErrorFromMapper<Em extends ErrorMapper<unknown>> = Em extends
|
|
(e: unknown) => infer E ? E : unknown;
|