@jumpaku/async-result
Version:
A typescript library that provides Result, Option, and AsyncResult.
266 lines (261 loc) • 8.83 kB
text/typescript
import { Result } from "./Result";
export class AsyncResult<V, E> implements PromiseLike<Result<V, E>> {
static of<V, E>(result: Result<V, E>): AsyncResult<V, E>;
static of<V, E>(result: Promise<Result<V, E>>): AsyncResult<V, unknown>;
static of<V, E, F>(
result: Promise<Result<V, E>>,
catchFun: (error: unknown) => F
): AsyncResult<V, E | F>;
static of<V, E, F>(
result: Result<V, E> | Promise<Result<V, E>>,
catchFun?: (error: unknown) => F
): AsyncResult<V, E | F | unknown> {
if (Result.isResult(result))
return new AsyncResult(Promise.resolve(result));
return catchFun == null
? new AsyncResult(result.catch((error: unknown) => Result.failure(error)))
: new AsyncResult<V, E | F>(
result.catch((error: unknown) => Result.failure(catchFun(error)))
);
}
static make<V, E>(
neverThrowExecutor: (
success: (value: V) => void,
failure: (error: E) => void
) => void
): AsyncResult<V, E> {
return AsyncResult.of(
new Promise<Result<V, E>>((resolve) =>
neverThrowExecutor(
(v: V) => resolve(Result.success(v)),
(e: E) => resolve(Result.failure(e))
)
),
(e) => e as E
);
}
static success<V>(v: V): AsyncResult<V, never> {
return AsyncResult.of(Result.success(v));
}
static failure<E>(e: E): AsyncResult<never, E> {
return AsyncResult.of(Result.failure(e));
}
static try<V>(tryFun: () => V | Promise<V>): AsyncResult<V, unknown>;
static try<V, E>(
tryFun: () => V | Promise<V>,
catchFun: (e: unknown) => E
): AsyncResult<V, E>;
static try<V, E>(
tryFun: () => V | Promise<V>,
catchFun?: (e: unknown) => E
): AsyncResult<V, E | unknown> {
const promise = new Promise<V>((resolve, reject) => {
try {
resolve(tryFun());
} catch (error) {
reject(error);
}
}).then(Result.success);
return catchFun == null
? AsyncResult.of(promise)
: AsyncResult.of(promise, catchFun);
}
private constructor(readonly promise: Promise<Result<V, E>>) {}
then<TResult1 = Result<V, E>, TResult2 = never>(
onfulfilled?:
| ((value: Result<V, E>) => TResult1 | PromiseLike<TResult1>)
| undefined
| null,
onrejected?:
| ((reason: unknown) => TResult2 | PromiseLike<TResult2>)
| undefined
| null
): Promise<TResult1 | TResult2> {
return this.promise.then(onfulfilled, onrejected);
}
match<X, Y>(
onSuccess: (value: V) => X,
onFailure: (error: E) => Y
): Promise<X | Y> {
return this.promise.then((result) => result.match(onSuccess, onFailure));
}
value(): Promise<V | undefined> {
return this.promise.then((r) => r.value);
}
error(): Promise<E | undefined> {
return this.promise.then((r) => r.error);
}
orNull(): Promise<V | null> {
return this.promise.then((r) => r.orNull());
}
orUndefined(): Promise<V | undefined> {
return this.promise.then((r) => r.orUndefined());
}
orReject<F>(f?: (e: E) => F): Promise<V> {
return this.promise.then((r) => (f == null ? r.orThrow() : r.orThrow(f)));
}
orDefault(value: V): Promise<V> {
return this.promise.then((r) => r.orDefault(value));
}
orRecover(neverThrowFun: (e: E) => V): Promise<V> {
return this.promise.then((r) => r.orRecover(neverThrowFun));
}
onSuccess(neverThrowFun: (value: V) => void): AsyncResult<V, E> {
this.promise.then((r) => r.onSuccess(neverThrowFun));
return this;
}
onFailure(neverThrowFun: (e: E) => void): AsyncResult<V, E> {
this.promise.then((r) => r.onFailure(neverThrowFun));
return this;
}
and<U, F>(other: AsyncResult<U, F>): AsyncResult<V | U, E | F> {
return new AsyncResult<V | U, E | F>(
this.promise.then((r0) => other.promise.then((r1) => r0.and(r1)))
);
}
or<U, F>(other: AsyncResult<U, F>): AsyncResult<V | U, E | F> {
return new AsyncResult<V | U, E | F>(
this.promise.then((r0) => other.promise.then((r1) => r0.or(r1)))
);
}
map<U>(neverThrowFun: (v: V) => U): AsyncResult<U, E> {
return new AsyncResult(
this.promise.then((result) => result.map(neverThrowFun))
);
}
tryMap<U>(tryFun: (v: V) => U): AsyncResult<U, unknown>;
tryMap<U, F>(
tryFun: (v: V) => U,
catchFun: (error: unknown) => F
): AsyncResult<U, E | F>;
tryMap<U, F>(
tryFun: (v: V) => U,
catchFun?: (error: unknown) => F
): AsyncResult<U, E | F | unknown> {
return (catchFun == null
? AsyncResult.try(() => this.promise.then((it) => it.tryMap(tryFun)))
: AsyncResult.try(
() => this.promise.then((it) => it.tryMap(tryFun, catchFun)),
catchFun
)
).flatMap((it) => AsyncResult.of(it));
}
flatMap<U, F>(
neverThrowFun: (v: V) => AsyncResult<U, F>
): AsyncResult<U, E | F> {
const promise = new Promise<Result<U, E | F>>(async (resolve) => {
(await this.promise)
.onSuccess((value) => resolve(neverThrowFun(value).promise))
.onFailure((error) => resolve(Result.failure(error)));
});
return new AsyncResult<U, E | F>(promise);
}
tryFlatMap<U, F>(
tryFun: (v: V) => AsyncResult<U, F>
): AsyncResult<U, unknown>;
tryFlatMap<U, F, G>(
tryFun: (v: V) => AsyncResult<U, F>,
catchFun: (error: unknown) => G
): AsyncResult<U, E | F | G>;
tryFlatMap<U, F, G>(
tryFun: (v: V) => AsyncResult<U, F>,
catchFun?: (error: unknown) => G
): AsyncResult<U, E | F | G | unknown> {
if (catchFun == null) {
const promise = this.promise
.then(async (r) =>
r.isFailure() ? r.castValue<U>() : tryFun(r.value).promise
)
.catch((error) => Result.failure<unknown>(error));
return AsyncResult.of<U, unknown>(promise);
} else {
const promise = this.promise
.then(async (r) =>
r.isFailure() ? r.castValue<U>() : tryFun(r.value).promise
)
.catch((error) => Result.failure<G>(catchFun(error)));
return AsyncResult.of<U, E | F | G>(promise);
}
}
recover(neverThrowFun: (e: E) => V): AsyncResult<V, never> {
return new AsyncResult<V, never>(
this.promise.then((result) => result.recover(neverThrowFun))
);
}
tryRecover(tryFun: (error: E) => V): AsyncResult<V, unknown>;
tryRecover<F>(
tryFun: (error: E) => V,
catchFun: (error: unknown) => F
): AsyncResult<V, F>;
tryRecover<F>(
tryFun: (error: E) => V,
catchFun?: (error: unknown) => F
): AsyncResult<V, F | unknown> {
return catchFun == null
? AsyncResult.of(
this.match(Result.success, (e) => Result.try(() => tryFun(e)))
)
: AsyncResult.of(
this.match(Result.success, (e) =>
Result.try(() => tryFun(e), catchFun)
)
);
}
flatRecover<F>(
neverThrowFun: (e: E) => AsyncResult<V, F>
): AsyncResult<V, F> {
const promise = new Promise<Result<V, F>>(async (resolve) => {
(await this.promise)
.onSuccess((value) => resolve(Result.success(value)))
.onFailure((error) => resolve(neverThrowFun(error).promise));
});
return new AsyncResult<V, F>(promise);
}
tryFlatRecover<F>(
tryFun: (error: E) => AsyncResult<V, F>
): AsyncResult<V, unknown>;
tryFlatRecover<F, G>(
tryFun: (error: E) => AsyncResult<V, F>,
catchFun: (error: unknown) => G
): AsyncResult<V, F>;
tryFlatRecover<F, G>(
tryFun: (error: E) => AsyncResult<V, F>,
catchFun?: (error: unknown) => G
): AsyncResult<V, F | G | unknown> {
const promise = new Promise<Result<V, F | G | unknown>>(async (resolve) => {
(await this.promise)
.onSuccess((value) => resolve(Result.success(value)))
.onFailure((error) => {
try {
resolve(tryFun(error).promise);
} catch (e: unknown) {
if (catchFun == null) resolve(Result.failure<unknown>(e));
else resolve(Result.failure<G>(catchFun(e)));
}
});
});
return new AsyncResult<V, F | G | unknown>(promise);
}
mapError<F>(neverThrowFun: (error: E) => F): AsyncResult<V, F> {
return new AsyncResult(
this.promise.then((result) => result.mapError(neverThrowFun))
);
}
tryMapError<F>(tryFun: (error: E) => F): AsyncResult<V, unknown>;
tryMapError<F, G>(
tryFun: (error: E) => F,
catchFun: (error: unknown) => G
): AsyncResult<V, F | G>;
tryMapError<F, G>(
tryFun: (error: E) => F,
catchFun?: (error: unknown) => G
): AsyncResult<V, F | G | unknown> {
return (catchFun == null
? AsyncResult.try(() => this.promise.then((it) => it.tryMapError(tryFun)))
: AsyncResult.try(
() => this.promise.then((it) => it.tryMapError(tryFun, catchFun)),
catchFun
)
).flatMap((it) => AsyncResult.of(it));
}
}