Skip to content

Commit

Permalink
feat: Support .catch method for Result (#23505)
Browse files Browse the repository at this point in the history
  • Loading branch information
zharinov committed Jul 22, 2023
1 parent 894a183 commit f28fc24
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 0 deletions.
78 changes: 78 additions & 0 deletions lib/util/result.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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));
});
});
});
});
81 changes: 81 additions & 0 deletions lib/util/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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);
}
}
}

/**
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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)

Check warning on line 564 in lib/util/result.ts

View workflow job for this annotation

GitHub Actions / lint-eslint

Avoid nesting promises.

Check warning on line 564 in lib/util/result.ts

View workflow job for this annotation

GitHub Actions / lint-eslint

Avoid nesting promises.
);
return AsyncResult.wrap(caughtAsyncResult);
}
}

0 comments on commit f28fc24

Please sign in to comment.