// deno-lint-ignore-file no-namespace no-explicit-any no-async-promise-executor import { UnexpectedError } from "@fabric/core"; import type { TaggedError } from "../error/tagged-error.ts"; import type { MaybePromise } from "../types/maybe-promise.ts"; import { Result } from "./result.ts"; /** * An AsyncResult represents the result of an asynchronous operation that can * resolve to a value of type `TValue` or an error of type `TError`. */ export class AsyncResult< TValue = any, TError extends TaggedError = never, > { static tryFrom( fn: () => MaybePromise, errorMapper: (error: any) => TError, ): AsyncResult { return new AsyncResult( new Promise>(async (resolve) => { try { const value = await fn(); resolve(Result.ok(value)); } catch (error) { resolve(Result.failWith(errorMapper(error))); } }), ); } static from(fn: () => MaybePromise): AsyncResult { return AsyncResult.tryFrom( fn, (error) => new UnexpectedError(error) as never, ); } static ok(value: T): AsyncResult { return new AsyncResult(Promise.resolve(Result.ok(value))); } static succeedWith = AsyncResult.ok; static failWith( error: TError, ): AsyncResult { return new AsyncResult(Promise.resolve(Result.failWith(error))); } private constructor(private r: Promise>) { } promise(): Promise> { return this.r; } async unwrapOrThrow(): Promise { return (await this.r).unwrapOrThrow(); } async orThrow(): Promise { return (await this.r).orThrow(); } async unwrapErrorOrThrow(): Promise { return (await this.r).unwrapErrorOrThrow(); } /** * Map a function over the value of the result. */ map( fn: (value: TValue) => TMappedValue, ): AsyncResult { return new AsyncResult( this.r.then((result) => result.map(fn)), ); } /** * Maps a function over the value of the result and flattens the result. */ flatMap( fn: (value: TValue) => AsyncResult, ): AsyncResult { return new AsyncResult( this.r.then((result) => { if (result.isError()) { return result as any; } return (fn(result.unwrapOrThrow())).promise(); }), ); } /** * Try to map a function over the value of the result. * If the function throws an error, the result will be a failure. */ tryMap( fn: (value: TValue) => TMappedValue, errMapper: (error: any) => TError, ): AsyncResult { return new AsyncResult( this.r.then((result) => result.tryMap(fn, errMapper)), ); } /** * Map a function over the error of the result. */ errorMap( fn: (error: TError) => TMappedError, ): AsyncResult { return new AsyncResult( this.r.then((result) => result.errorMap(fn)), ); } /** * Execute a function if the result is not an error. * The function does not affect the result. */ tap(fn: (value: TValue) => void): AsyncResult { return new AsyncResult( this.r.then((result) => result.tap(fn)), ); } assert( fn: (value: TValue) => AsyncResult, ): AsyncResult { return new AsyncResult( this.r.then((result) => { if (result.isError()) { return result as any; } return (fn(result.unwrapOrThrow())).promise(); }), ); } }