fix(util/result): Types for wrapNullable (#23713)

This commit is contained in:
Sergei Zharinov 2023-08-04 18:00:11 +03:00 committed by GitHub
parent 77952db8d9
commit 8c0013f1fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 178 additions and 131 deletions

View file

@ -345,7 +345,7 @@ export function getRawPkgReleases(
return AsyncResult.err('no-package-name'); return AsyncResult.err('no-package-name');
} }
return Result.wrapNullable(getRawReleases(config), 'no-result') return Result.wrapNullable(getRawReleases(config), 'no-result' as const)
.catch((e) => { .catch((e) => {
if (e instanceof ExternalHostError) { if (e instanceof ExternalHostError) {
e.hostType = config.datasource; e.hostType = config.datasource;

View file

@ -13,10 +13,10 @@ import { MetadataCache } from './metadata-cache';
import { GemInfo, MarshalledVersionInfo } from './schema'; import { GemInfo, MarshalledVersionInfo } from './schema';
import { VersionsEndpointCache } from './versions-endpoint-cache'; import { VersionsEndpointCache } from './versions-endpoint-cache';
function unlessServerSide<T, E>( function unlessServerSide<
err: E, T extends NonNullable<unknown>,
cb: () => AsyncResult<T, E> E extends NonNullable<unknown>
): AsyncResult<T, E> { >(err: E, cb: () => AsyncResult<T, E>): AsyncResult<T, E> {
if (err instanceof HttpError && err.response?.statusCode) { if (err instanceof HttpError && err.response?.statusCode) {
const code = err.response.statusCode; const code = err.response.statusCode;
if (code >= 500 && code <= 599) { if (code >= 500 && code <= 599) {

View file

@ -23,14 +23,14 @@ export class MetadataCache {
const cacheKey = `metadata-cache:${registryUrl}:${packageName}`; const cacheKey = `metadata-cache:${registryUrl}:${packageName}`;
const hash = toSha256(versions.join('')); const hash = toSha256(versions.join(''));
const loadCache = (): AsyncResult<ReleaseResult, unknown> => const loadCache = (): AsyncResult<ReleaseResult, NonNullable<unknown>> =>
Result.wrapNullable( Result.wrapNullable(
packageCache.get<CacheRecord>(cacheNs, cacheKey), packageCache.get<CacheRecord>(cacheNs, cacheKey),
'cache-not-found' 'cache-not-found' as const
).transform((cache) => { ).transform((cache) => {
return hash === cache.hash return hash === cache.hash
? Result.ok(cache.data) ? Result.ok(cache.data)
: Result.err('cache-outdated'); : Result.err('cache-outdated' as const);
}); });
const saveCache = async (data: ReleaseResult): Promise<ReleaseResult> => { const saveCache = async (data: ReleaseResult): Promise<ReleaseResult> => {

View file

@ -40,7 +40,10 @@ describe('util/result', () => {
}); });
it('wraps nullable callback', () => { it('wraps nullable callback', () => {
const res = Result.wrapNullable(() => 42, 'oops'); const res: Result<number, 'oops'> = Result.wrapNullable(
(): number | null => 42,
'oops'
);
expect(res).toEqual(Result.ok(42)); expect(res).toEqual(Result.ok(42));
}); });
@ -225,7 +228,10 @@ describe('util/result', () => {
}); });
it('wraps nullable promise', async () => { it('wraps nullable promise', async () => {
const res = Result.wrapNullable(Promise.resolve(42), 'oops'); const res: AsyncResult<number, 'oops'> = Result.wrapNullable(
Promise.resolve<number | null>(42),
'oops'
);
await expect(res).resolves.toEqual(Result.ok(42)); await expect(res).resolves.toEqual(Result.ok(42));
}); });

View file

@ -1,15 +1,18 @@
import { SafeParseReturnType, ZodError } from 'zod'; import { SafeParseReturnType, ZodError } from 'zod';
import { logger } from '../logger'; import { logger } from '../logger';
interface Ok<T> { type Val = NonNullable<unknown>;
type Nullable<T extends Val> = T | null | undefined;
interface Ok<T extends Val> {
readonly ok: true; readonly ok: true;
readonly val: NonNullable<T>; readonly val: T;
readonly err?: never; readonly err?: never;
} }
interface Err<E> { interface Err<E extends Val> {
readonly ok: false; readonly ok: false;
readonly err: NonNullable<E>; readonly err: E;
readonly val?: never; readonly val?: never;
/** /**
@ -19,11 +22,11 @@ interface Err<E> {
readonly _uncaught?: true; readonly _uncaught?: true;
} }
type Res<T, E> = Ok<T> | Err<E>; type Res<T extends Val, E extends Val> = Ok<T> | Err<E>;
function isZodResult<Input, Output>( function isZodResult<Input, Output extends Val>(
input: unknown input: unknown
): input is SafeParseReturnType<Input, NonNullable<Output>> { ): input is SafeParseReturnType<Input, Output> {
if ( if (
typeof input !== 'object' || typeof input !== 'object' ||
input === null || input === null ||
@ -45,9 +48,9 @@ function isZodResult<Input, Output>(
} }
} }
function fromZodResult<Input, Output>( function fromZodResult<ZodInput, ZodOutput extends Val>(
input: SafeParseReturnType<Input, NonNullable<Output>> input: SafeParseReturnType<ZodInput, ZodOutput>
): Result<Output, ZodError<Input>> { ): Result<ZodOutput, ZodError<ZodInput>> {
return input.success ? Result.ok(input.data) : Result.err(input.error); return input.success ? Result.ok(input.data) : Result.err(input.error);
} }
@ -55,9 +58,9 @@ function fromZodResult<Input, Output>(
* All non-nullable values that also are not Promises nor Zod results. * All non-nullable values that also are not Promises nor Zod results.
* It's useful for restricting Zod results to not return `null` or `undefined`. * It's useful for restricting Zod results to not return `null` or `undefined`.
*/ */
type RawValue<T> = Exclude< type RawValue<T extends Val> = Exclude<
NonNullable<T>, T,
SafeParseReturnType<unknown, NonNullable<T>> | Promise<unknown> SafeParseReturnType<unknown, T> | Promise<unknown>
>; >;
/** /**
@ -68,18 +71,18 @@ type RawValue<T> = Exclude<
* - `.transform()` are pipes which can be chained * - `.transform()` are pipes which can be chained
* - `.unwrap()` is the point of consumption * - `.unwrap()` is the point of consumption
*/ */
export class Result<T, E = Error> { export class Result<T extends Val, E extends Val = Error> {
private constructor(private readonly res: Res<T, E>) {} private constructor(private readonly res: Res<T, E>) {}
static ok<T>(val: NonNullable<T>): Result<T, never> { static ok<T extends Val>(val: T): Result<T, never> {
return new Result({ ok: true, val }); return new Result({ ok: true, val });
} }
static err<E>(err: NonNullable<E>): Result<never, E> { static err<E extends Val>(err: E): Result<never, E> {
return new Result({ ok: false, err }); return new Result({ ok: false, err });
} }
static _uncaught<E>(err: NonNullable<E>): Result<never, E> { static _uncaught<E extends Val>(err: E): Result<never, E> {
return new Result({ ok: false, err, _uncaught: true }); return new Result({ ok: false, err, _uncaught: true });
} }
@ -112,17 +115,26 @@ export class Result<T, E = Error> {
* *
* ``` * ```
*/ */
static wrap<T, Input = any>( static wrap<T extends Val, Input = any>(
zodResult: SafeParseReturnType<Input, NonNullable<T>> zodResult: SafeParseReturnType<Input, T>
): Result<T, ZodError<Input>>; ): Result<T, ZodError<Input>>;
static wrap<T, E = Error>(callback: () => RawValue<T>): Result<T, E>; static wrap<T extends Val, E extends Val = Error>(
static wrap<T, E = Error, EE = never>( callback: () => RawValue<T>
): Result<T, E>;
static wrap<T extends Val, E extends Val = Error, EE extends Val = never>(
promise: Promise<Result<T, EE>> promise: Promise<Result<T, EE>>
): AsyncResult<T, E | EE>; ): AsyncResult<T, E | EE>;
static wrap<T, E = Error>(promise: Promise<RawValue<T>>): AsyncResult<T, E>; static wrap<T extends Val, E extends Val = Error>(
static wrap<T, E = Error, EE = never, Input = any>( promise: Promise<RawValue<T>>
): AsyncResult<T, E>;
static wrap<
T extends Val,
E extends Val = Error,
EE extends Val = never,
Input = any
>(
input: input:
| SafeParseReturnType<Input, NonNullable<T>> | SafeParseReturnType<Input, T>
| (() => RawValue<T>) | (() => RawValue<T>)
| Promise<Result<T, EE>> | Promise<Result<T, EE>>
| Promise<RawValue<T>> | Promise<RawValue<T>>
@ -151,7 +163,7 @@ export class Result<T, E = Error> {
* hence never re-thrown. * hence never re-thrown.
* *
* Since functions and promises returning nullable can't be wrapped with `Result.wrap()` * Since functions and promises returning nullable can't be wrapped with `Result.wrap()`
* because `val` is constrained by being `NonNullable<T>`, `null` and `undefined` * because `val` is constrained by being `NonNullable`, `null` and `undefined`
* must be converted to some sort of `err` value. * must be converted to some sort of `err` value.
* *
* This method does exactly this, i.g. it is the feature-rich shorthand for: * This method does exactly this, i.g. it is the feature-rich shorthand for:
@ -187,47 +199,70 @@ export class Result<T, E = Error> {
* *
* ``` * ```
*/ */
static wrapNullable<T, E = Error, NullableError = Error>( static wrapNullable<
callback: () => T, T extends Val,
nullableError: NonNullable<NullableError> E extends Val = Error,
): Result<T, E | NullableError>; ErrForNullable extends Val = Error
static wrapNullable<T, E = Error, NullError = Error, UndefinedError = Error>( >(
callback: () => T, callback: () => Nullable<T>,
nullError: NonNullable<NullError>, errForNullable: ErrForNullable
undefinedError: NonNullable<UndefinedError> ): Result<T, E | ErrForNullable>;
): Result<T, E | NullError | UndefinedError>; static wrapNullable<
static wrapNullable<T, E = Error, NullableError = Error>( T extends Val,
promise: Promise<T>, E extends Val = Error,
nullableError: NonNullable<NullableError> ErrForNull extends Val = Error,
): AsyncResult<T, E | NullableError>; ErrForUndefined extends Val = Error
static wrapNullable<T, E = Error, NullError = Error, UndefinedError = Error>( >(
promise: Promise<T>, callback: () => Nullable<T>,
nullError: NonNullable<NullError>, errForNull: ErrForNull,
undefinedError: NonNullable<UndefinedError> errForUndefined: ErrForUndefined
): AsyncResult<T, E | NullError | UndefinedError>; ): Result<T, E | ErrForNull | ErrForUndefined>;
static wrapNullable<T, E = Error, NullError = Error, UndefinedError = Error>( static wrapNullable<
input: (() => T) | Promise<T>, T extends Val,
arg2: NonNullable<NullError>, E extends Val = Error,
arg3?: NonNullable<UndefinedError> ErrForNullable extends Val = Error
>(
promise: Promise<Nullable<T>>,
errForNullable: ErrForNullable
): AsyncResult<T, E | ErrForNullable>;
static wrapNullable<
T extends Val,
E extends Val = Error,
ErrForNull extends Val = Error,
ErrForUndefined extends Val = Error
>(
promise: Promise<Nullable<T>>,
errForNull: ErrForNull,
errForUndefined: ErrForUndefined
): AsyncResult<T, E | ErrForNull | ErrForUndefined>;
static wrapNullable<
T extends Val,
E extends Val = Error,
ErrForNull extends Val = Error,
ErrForUndefined extends Val = Error
>(
input: (() => Nullable<T>) | Promise<Nullable<T>>,
arg2: ErrForNull,
arg3?: ErrForUndefined
): ):
| Result<T, E | NullError | UndefinedError> | Result<T, E | ErrForNull | ErrForUndefined>
| AsyncResult<T, E | NullError | UndefinedError> { | AsyncResult<T, E | ErrForNull | ErrForUndefined> {
const nullError = arg2; const errForNull = arg2;
const undefinedError = arg3 ?? arg2; const errForUndefined = arg3 ?? arg2;
if (input instanceof Promise) { if (input instanceof Promise) {
return AsyncResult.wrapNullable(input, nullError, undefinedError); return AsyncResult.wrapNullable(input, errForNull, errForUndefined);
} }
try { try {
const result = input(); const result = input();
if (result === null) { if (result === null) {
return Result.err(nullError); return Result.err(errForNull);
} }
if (result === undefined) { if (result === undefined) {
return Result.err(undefinedError); return Result.err(errForUndefined);
} }
return Result.ok(result); return Result.ok(result);
@ -255,8 +290,8 @@ export class Result<T, E = Error> {
* ``` * ```
*/ */
unwrap(): Res<T, E>; unwrap(): Res<T, E>;
unwrap(fallback: NonNullable<T>): NonNullable<T>; unwrap(fallback: T): T;
unwrap(fallback?: NonNullable<T>): Res<T, E> | NonNullable<T> { unwrap(fallback?: T): Res<T, E> | T {
if (this.res.ok) { if (this.res.ok) {
return fallback === undefined ? this.res : this.res.val; return fallback === undefined ? this.res : this.res.val;
} }
@ -275,7 +310,7 @@ export class Result<T, E = Error> {
/** /**
* Returns the ok-value or throw the error. * Returns the ok-value or throw the error.
*/ */
unwrapOrThrow(): NonNullable<T> { unwrapOrThrow(): T {
if (this.res.ok) { if (this.res.ok) {
return this.res.val; return this.res.val;
} }
@ -309,30 +344,28 @@ export class Result<T, E = Error> {
* *
* ``` * ```
*/ */
transform<U, EE>( transform<U extends Val, EE extends Val>(
fn: (value: NonNullable<T>) => Result<U, E | EE> fn: (value: T) => Result<U, E | EE>
): Result<U, E | EE>; ): Result<U, E | EE>;
transform<U, EE>( transform<U extends Val, EE extends Val>(
fn: (value: NonNullable<T>) => AsyncResult<U, E | EE> fn: (value: T) => AsyncResult<U, E | EE>
): AsyncResult<U, E | EE>; ): AsyncResult<U, E | EE>;
transform<U, Input = any>( transform<U extends Val, Input = any>(
fn: (value: NonNullable<T>) => SafeParseReturnType<Input, NonNullable<U>> fn: (value: T) => SafeParseReturnType<Input, NonNullable<U>>
): Result<U, E | ZodError<Input>>; ): Result<U, E | ZodError<Input>>;
transform<U, Input = any>( transform<U extends Val, Input = any>(
fn: ( fn: (value: T) => Promise<SafeParseReturnType<Input, NonNullable<U>>>
value: NonNullable<T>
) => Promise<SafeParseReturnType<Input, NonNullable<U>>>
): AsyncResult<U, E | ZodError<Input>>; ): AsyncResult<U, E | ZodError<Input>>;
transform<U, EE>( transform<U extends Val, EE extends Val>(
fn: (value: NonNullable<T>) => Promise<Result<U, E | EE>> fn: (value: T) => Promise<Result<U, E | EE>>
): AsyncResult<U, E | EE>; ): AsyncResult<U, E | EE>;
transform<U>( transform<U extends Val>(
fn: (value: NonNullable<T>) => Promise<RawValue<U>> fn: (value: T) => Promise<RawValue<U>>
): AsyncResult<U, E>; ): AsyncResult<U, E>;
transform<U>(fn: (value: NonNullable<T>) => RawValue<U>): Result<U, E>; transform<U extends Val>(fn: (value: T) => RawValue<U>): Result<U, E>;
transform<U, EE, Input = any>( transform<U extends Val, EE extends Val, Input = any>(
fn: ( fn: (
value: NonNullable<T> value: T
) => ) =>
| Result<U, E | EE> | Result<U, E | EE>
| AsyncResult<U, E | EE> | AsyncResult<U, E | EE>
@ -377,18 +410,18 @@ export class Result<T, E = Error> {
} }
} }
catch<U = T, EE = E>( catch<U extends Val = T, EE extends Val = E>(
fn: (err: NonNullable<E>) => Result<U, E | EE> fn: (err: E) => Result<U, E | EE>
): Result<T | U, E | EE>; ): Result<T | U, E | EE>;
catch<U = T, EE = E>( catch<U extends Val = T, EE extends Val = E>(
fn: (err: NonNullable<E>) => AsyncResult<U, E | EE> fn: (err: E) => AsyncResult<U, E | EE>
): AsyncResult<T | U, E | EE>; ): AsyncResult<T | U, E | EE>;
catch<U = T, EE = E>( catch<U extends Val = T, EE extends Val = E>(
fn: (err: NonNullable<E>) => Promise<Result<U, E | EE>> fn: (err: E) => Promise<Result<U, E | EE>>
): AsyncResult<T | U, E | EE>; ): AsyncResult<T | U, E | EE>;
catch<U = T, EE = E>( catch<U extends Val = T, EE extends Val = E>(
fn: ( fn: (
err: NonNullable<E> err: E
) => Result<U, E | EE> | AsyncResult<U, E | EE> | Promise<Result<U, E | EE>> ) => Result<U, E | EE> | AsyncResult<U, E | EE> | Promise<Result<U, E | EE>>
): Result<T | U, E | EE> | AsyncResult<T | U, E | EE> { ): Result<T | U, E | EE> | AsyncResult<T | U, E | EE> {
if (this.res.ok) { if (this.res.ok) {
@ -426,7 +459,9 @@ export class Result<T, E = Error> {
* *
* All the methods resemble `Result` methods, but work asynchronously. * All the methods resemble `Result` methods, but work asynchronously.
*/ */
export class AsyncResult<T, E> implements PromiseLike<Result<T, E>> { export class AsyncResult<T extends Val, E extends Val>
implements PromiseLike<Result<T, E>>
{
private constructor(private asyncResult: Promise<Result<T, E>>) {} private constructor(private asyncResult: Promise<Result<T, E>>) {}
then<TResult1 = Result<T, E>>( then<TResult1 = Result<T, E>>(
@ -438,18 +473,23 @@ export class AsyncResult<T, E> implements PromiseLike<Result<T, E>> {
return this.asyncResult.then(onfulfilled); return this.asyncResult.then(onfulfilled);
} }
static ok<T>(val: NonNullable<T>): AsyncResult<T, never> { static ok<T extends Val>(val: T): AsyncResult<T, never> {
return new AsyncResult(Promise.resolve(Result.ok(val))); return new AsyncResult(Promise.resolve(Result.ok(val)));
} }
static err<E>(err: NonNullable<E>): AsyncResult<never, E> { static err<E extends Val>(err: NonNullable<E>): AsyncResult<never, E> {
// eslint-disable-next-line promise/no-promise-in-callback // eslint-disable-next-line promise/no-promise-in-callback
return new AsyncResult(Promise.resolve(Result.err(err))); return new AsyncResult(Promise.resolve(Result.err(err)));
} }
static wrap<T, E = Error, EE = never, Input = any>( static wrap<
T extends Val,
E extends Val = Error,
EE extends Val = never,
Input = any
>(
promise: promise:
| Promise<SafeParseReturnType<Input, NonNullable<T>>> | Promise<SafeParseReturnType<Input, T>>
| Promise<Result<T, EE>> | Promise<Result<T, EE>>
| Promise<RawValue<T>>, | Promise<RawValue<T>>,
onErr?: (err: NonNullable<E>) => Result<T, E> onErr?: (err: NonNullable<E>) => Result<T, E>
@ -476,20 +516,25 @@ export class AsyncResult<T, E> implements PromiseLike<Result<T, E>> {
); );
} }
static wrapNullable<T, E, NullError, UndefinedError>( static wrapNullable<
promise: Promise<T>, T extends Val,
nullError: NonNullable<NullError>, E extends Val,
undefinedError: NonNullable<UndefinedError> ErrForNull extends Val,
): AsyncResult<T, E | NullError | UndefinedError> { ErrForUndefined extends Val
>(
promise: Promise<Nullable<T>>,
errForNull: NonNullable<ErrForNull>,
errForUndefined: NonNullable<ErrForUndefined>
): AsyncResult<T, E | ErrForNull | ErrForUndefined> {
return new AsyncResult( return new AsyncResult(
promise promise
.then((value) => { .then((value) => {
if (value === null) { if (value === null) {
return Result.err(nullError); return Result.err(errForNull);
} }
if (value === undefined) { if (value === undefined) {
return Result.err(undefinedError); return Result.err(errForUndefined);
} }
return Result.ok(value); return Result.ok(value);
@ -517,19 +562,17 @@ export class AsyncResult<T, E> implements PromiseLike<Result<T, E>> {
* ``` * ```
*/ */
unwrap(): Promise<Res<T, E>>; unwrap(): Promise<Res<T, E>>;
unwrap(fallback: NonNullable<T>): Promise<NonNullable<T>>; unwrap(fallback: T): Promise<T>;
unwrap( unwrap(fallback?: T): Promise<Res<T, E>> | Promise<T> {
fallback?: NonNullable<T>
): Promise<Res<T, E>> | Promise<NonNullable<T>> {
return fallback === undefined return fallback === undefined
? this.asyncResult.then<Res<T, E>>((res) => res.unwrap()) ? this.asyncResult.then<Res<T, E>>((res) => res.unwrap())
: this.asyncResult.then<NonNullable<T>>((res) => res.unwrap(fallback)); : this.asyncResult.then<T>((res) => res.unwrap(fallback));
} }
/** /**
* Returns the ok-value or throw the error. * Returns the ok-value or throw the error.
*/ */
async unwrapOrThrow(): Promise<NonNullable<T>> { async unwrapOrThrow(): Promise<T> {
const result = await this.asyncResult; const result = await this.asyncResult;
return result.unwrapOrThrow(); return result.unwrapOrThrow();
} }
@ -553,30 +596,28 @@ export class AsyncResult<T, E> implements PromiseLike<Result<T, E>> {
* *
* ``` * ```
*/ */
transform<U, EE>( transform<U extends Val, EE extends Val>(
fn: (value: NonNullable<T>) => Result<U, E | EE> fn: (value: T) => Result<U, E | EE>
): AsyncResult<U, E | EE>; ): AsyncResult<U, E | EE>;
transform<U, EE>( transform<U extends Val, EE extends Val>(
fn: (value: NonNullable<T>) => AsyncResult<U, E | EE> fn: (value: T) => AsyncResult<U, E | EE>
): AsyncResult<U, E | EE>; ): AsyncResult<U, E | EE>;
transform<U, Input = any>( transform<U extends Val, Input = any>(
fn: (value: NonNullable<T>) => SafeParseReturnType<Input, NonNullable<U>> fn: (value: T) => SafeParseReturnType<Input, NonNullable<U>>
): AsyncResult<U, E | ZodError<Input>>; ): AsyncResult<U, E | ZodError<Input>>;
transform<U, Input = any>( transform<U extends Val, Input = any>(
fn: ( fn: (value: T) => Promise<SafeParseReturnType<Input, NonNullable<U>>>
value: NonNullable<T>
) => Promise<SafeParseReturnType<Input, NonNullable<U>>>
): AsyncResult<U, E | ZodError<Input>>; ): AsyncResult<U, E | ZodError<Input>>;
transform<U, EE>( transform<U extends Val, EE extends Val>(
fn: (value: NonNullable<T>) => Promise<Result<U, E | EE>> fn: (value: T) => Promise<Result<U, E | EE>>
): AsyncResult<U, E | EE>; ): AsyncResult<U, E | EE>;
transform<U>( transform<U extends Val>(
fn: (value: NonNullable<T>) => Promise<RawValue<U>> fn: (value: T) => Promise<RawValue<U>>
): AsyncResult<U, E>; ): AsyncResult<U, E>;
transform<U>(fn: (value: NonNullable<T>) => RawValue<U>): AsyncResult<U, E>; transform<U extends Val>(fn: (value: T) => RawValue<U>): AsyncResult<U, E>;
transform<U, EE, Input = any>( transform<U extends Val, EE extends Val, Input = any>(
fn: ( fn: (
value: NonNullable<T> value: T
) => ) =>
| Result<U, E | EE> | Result<U, E | EE>
| AsyncResult<U, E | EE> | AsyncResult<U, E | EE>
@ -632,16 +673,16 @@ export class AsyncResult<T, E> implements PromiseLike<Result<T, E>> {
); );
} }
catch<U = T, EE = E>( catch<U extends Val = T, EE extends Val = E>(
fn: (err: NonNullable<E>) => Result<U, E | EE> fn: (err: NonNullable<E>) => Result<U, E | EE>
): AsyncResult<T | U, E | EE>; ): AsyncResult<T | U, E | EE>;
catch<U = T, EE = E>( catch<U extends Val = T, EE extends Val = E>(
fn: (err: NonNullable<E>) => AsyncResult<U, E | EE> fn: (err: NonNullable<E>) => AsyncResult<U, E | EE>
): AsyncResult<T | U, E | EE>; ): AsyncResult<T | U, E | EE>;
catch<U = T, EE = E>( catch<U extends Val = T, EE extends Val = E>(
fn: (err: NonNullable<E>) => Promise<Result<U, E | EE>> fn: (err: NonNullable<E>) => Promise<Result<U, E | EE>>
): AsyncResult<T | U, E | EE>; ): AsyncResult<T | U, E | EE>;
catch<U = T, EE = E>( catch<U extends Val = T, EE extends Val = E>(
fn: ( fn: (
err: NonNullable<E> err: NonNullable<E>
) => Result<U, E | EE> | AsyncResult<U, E | EE> | Promise<Result<U, E | EE>> ) => Result<U, E | EE> | AsyncResult<U, E | EE> | Promise<Result<U, E | EE>>