feat: Support .catch method for Result (#23505)

This commit is contained in:
Sergei Zharinov 2023-07-22 16:16:10 +03:00 committed by GitHub
parent 894a18354b
commit f28fc24201
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 159 additions and 0 deletions

View file

@ -107,6 +107,16 @@ describe('util/result', () => {
.unwrap()
).toThrow('oops');
});
it('returns ok-value for unwrapOrThrow', () => {
const res = Result.ok(42);
expect(res.unwrapOrThrow()).toBe(42);
});
it('throws error for unwrapOrThrow on error result', () => {
const res = Result.err('oops');
expect(() => res.unwrapOrThrow()).toThrow('oops');
});
});
describe('Transforming', () => {
@ -140,6 +150,36 @@ describe('util/result', () => {
);
});
});
describe('Catch', () => {
it('bypasses ok result', () => {
const res = Result.ok(42);
expect(res.catch(() => Result.ok(0))).toEqual(Result.ok(42));
expect(res.catch(() => Result.ok(0))).toBe(res);
});
it('bypasses uncaught transform errors', () => {
const res = Result.ok(42).transform(() => {
throw 'oops';
});
expect(res.catch(() => Result.ok(0))).toEqual(Result._uncaught('oops'));
expect(res.catch(() => Result.ok(0))).toBe(res);
});
it('converts error to Result', () => {
const result = Result.err<string>('oops').catch(() =>
Result.ok<number>(42)
);
expect(result).toEqual(Result.ok(42));
});
it('handles error thrown in catch function', () => {
const result = Result.err<string>('oops').catch(() => {
throw 'oops';
});
expect(result).toEqual(Result._uncaught('oops'));
});
});
});
describe('AsyncResult', () => {
@ -222,6 +262,16 @@ describe('util/result', () => {
const res = Result.wrap(Promise.reject('oops'));
await expect(res.unwrap(42)).resolves.toBe(42);
});
it('returns ok-value for unwrapOrThrow', async () => {
const res = Result.wrap(Promise.resolve(42));
await expect(res.unwrapOrThrow()).resolves.toBe(42);
});
it('rejects for error for unwrapOrThrow', async () => {
const res = Result.wrap(Promise.reject('oops'));
await expect(res.unwrapOrThrow()).rejects.toBe('oops');
});
});
describe('Transforming', () => {
@ -367,5 +417,33 @@ describe('util/result', () => {
expect(res).toEqual(Result.ok('F-O-O'));
});
});
describe('Catch', () => {
it('converts error to AsyncResult', async () => {
const result = await Result.err<string>('oops').catch(() =>
AsyncResult.ok(42)
);
expect(result).toEqual(Result.ok(42));
});
it('converts error to Promise', async () => {
const fallback = Promise.resolve(Result.ok(42));
const result = await Result.err<string>('oops').catch(() => fallback);
expect(result).toEqual(Result.ok(42));
});
it('handles error thrown in Promise result', async () => {
const fallback = Promise.reject('oops');
const result = await Result.err<string>('oops').catch(() => fallback);
expect(result).toEqual(Result._uncaught('oops'));
});
it('converts AsyncResult error to Result', async () => {
const result = await AsyncResult.err<string>('oops').catch(() =>
AsyncResult.ok<number>(42)
);
expect(result).toEqual(Result.ok(42));
});
});
});
});

View file

@ -226,6 +226,17 @@ export class Result<T, E = Error> {
return this.res;
}
/**
* Returns the ok-value or throw the error.
*/
unwrapOrThrow(): NonNullable<T> {
if (this.res.ok) {
return this.res.val;
}
throw this.res.err;
}
/**
* Transforms the ok-value, sync or async way.
*
@ -301,6 +312,48 @@ export class Result<T, E = Error> {
return Result._uncaught(err);
}
}
catch<U = T, EE = E>(
fn: (err: NonNullable<E>) => Result<U, E | EE>
): Result<T | U, E | EE>;
catch<U = T, EE = E>(
fn: (err: NonNullable<E>) => AsyncResult<U, E | EE>
): AsyncResult<T | U, E | EE>;
catch<U = T, EE = E>(
fn: (err: NonNullable<E>) => Promise<Result<U, E | EE>>
): AsyncResult<T | U, E | EE>;
catch<U = T, EE = E>(
fn: (
err: NonNullable<E>
) => Result<U, E | EE> | AsyncResult<U, E | EE> | Promise<Result<U, E | EE>>
): Result<T | U, E | EE> | AsyncResult<T | U, E | EE> {
if (this.res.ok) {
return this;
}
if (this.res._uncaught) {
return this;
}
try {
const result = fn(this.res.err);
if (result instanceof Promise) {
return AsyncResult.wrap(result, (err) => {
logger.warn(
{ err },
'Result: unexpected error in async catch handler'
);
return Result._uncaught(err);
});
}
return result;
} catch (err) {
logger.warn({ err }, 'Result: unexpected error in catch handler');
return Result._uncaught(err);
}
}
}
/**
@ -401,6 +454,14 @@ export class AsyncResult<T, E> implements PromiseLike<Result<T, E>> {
: this.asyncResult.then<NonNullable<T>>((res) => res.unwrap(fallback));
}
/**
* Returns the ok-value or throw the error.
*/
async unwrapOrThrow(): Promise<NonNullable<T>> {
const result = await this.asyncResult;
return result.unwrapOrThrow();
}
/**
* Transforms the ok-value, sync or async way.
*
@ -484,4 +545,24 @@ export class AsyncResult<T, E> implements PromiseLike<Result<T, E>> {
})
);
}
catch<U = T, EE = E>(
fn: (err: NonNullable<E>) => Result<U, E | EE>
): AsyncResult<T | U, E | EE>;
catch<U = T, EE = E>(
fn: (err: NonNullable<E>) => AsyncResult<U, E | EE>
): AsyncResult<T | U, E | EE>;
catch<U = T, EE = E>(
fn: (err: NonNullable<E>) => Promise<Result<U, E | EE>>
): AsyncResult<T | U, E | EE>;
catch<U = T, EE = E>(
fn: (
err: NonNullable<E>
) => Result<U, E | EE> | AsyncResult<U, E | EE> | Promise<Result<U, E | EE>>
): AsyncResult<T | U, E | EE> {
const caughtAsyncResult = this.asyncResult.then((result) =>
result.catch(fn as never)
);
return AsyncResult.wrap(caughtAsyncResult);
}
}