seangwright / typescript-functional-extensions

A TypeScript implementation of the C# library CSharpFunctionalExtensions, including synchronous and asynchronous Maybe and Result monads.
MIT License
33 stars 4 forks source link

Feature: add bindFailure #20

Closed GregOnNet closed 1 year ago

GregOnNet commented 1 year ago

Is your feature request related to a problem? Please describe.

if (coindition_1) {
  return value_1;
}

if(condition_2) {
  return value_2
}

return null;
function condition_1(): Result {}
function condition_2(): Result {}

I was not able to figure out how to achieve this with the built-in operators. Maybe I am thinking too procedural, here?

Describe the solution you'd like It would be nice having an operator, allowing to execute a function that returns a Result, if the predecessor-Result is a failure.

Describe alternatives you've considered

Additional context I am wondering if it is beneficial to add an operator to the library's core. Possibly there is already a better way, I am not able not see.

Thank you so much, Cheers Gregor

GregOnNet commented 1 year ago

Hi,

today I was facing this use case again. This time ResultAsync was involved, that's why I wrote an async version of tryRecoverIfFailure.

Not sure if this is the best possible implementation. I post both implementations here, for documentation purpose.

Result

export function tryRecoverIfFailure<TValue, TError>(projection: FunctionOfT<Result<TValue, TError>>): ResultOpFn<TValue, TError, TValue, TError> {
  return (result) => {
    return result.isFailure ? projection() : result;
  };
}

ResultAsync

export function tryRecoverIfFailureAsync<TValue, TError>(
  projection: FunctionOfT<ResultAsync<TValue, TError>>,
): ResultAsyncOpFn<TValue, TError, TValue, TError> {
  return (resultAsync) => {
    return ResultAsync.from(resultAsync.isSuccess.then((isSuccess) => (isSuccess ? resultAsync.toPromise() : projection().toPromise())));
  };
}
Unit Tests describe('typescript-functional-extensions', () => { describe('recoverIfFailureAsync', () => { describe('When the previous result fails', () => { it('takes the result from the second result', async () => { const resultAsyncRecoverySuccess = () => ResultAsync.success('✅'); const resultAsync = ResultAsync.failure('💥').pipe(tryRecoverIfFailureAsync(() => resultAsyncRecoverySuccess())); const result = await resultAsync.toPromise(); expect(result.getValueOrThrow()).toBe('✅'); }); }); describe('When the previous result succeeds', () => { it('takes its result', async () => { const resultAsyncRecoveryIrrelevant = () => ResultAsync.failure('🤷‍♂️'); const resultAsync = ResultAsync.success('✅').pipe(tryRecoverIfFailureAsync(() => resultAsyncRecoveryIrrelevant())); const result = await resultAsync.toPromise(); expect(result.getValueOrThrow()).toBe('✅'); }); }); describe('When both results fail', () => { it('takes the failure from the second result', async () => { const resultAsyncRecoveryFailure = () => ResultAsync.failure('💥💥'); const resultAsync = ResultAsync.failure('💥').pipe(tryRecoverIfFailureAsync(() => resultAsyncRecoveryFailure())); const result = await resultAsync.toPromise(); expect(result.getErrorOrThrow()).toBe('💥💥'); }); }); }); });
GregOnNet commented 1 year ago

Short Update:

In my project, I renamed the operators to:

This naming makes clear that the projection-function expects a ResultAsync.

seangwright commented 1 year ago

I'd take a PR for those 👍 - I like the name and I think they could be useful to more developers.

GregOnNet commented 1 year ago

That's great to read.

You can assign me to this issue. I will prepare the PR. 🥳

GregOnNet commented 1 year ago

@seangwright, PR #22 available

seangwright commented 1 year ago

Resolved with #22