seangwright / typescript-functional-extensions

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

ResultAsync is not flattened in Monad-Chain #9

Closed GregOnNet closed 2 years ago

GregOnNet commented 2 years ago

Describe the bug I have a ResultChain that calls a Method returning ResultAsync. My expectation is, that ResultAsync is transparent for the Chain, but ResultAsync is passed to the next operator.

To Reproduce

class Processor {
  process() {
    this.fetchAnyA()
      .ensure((fetchedA) => !fetchedA, 'Cannot process value already exists')
      .map(() => this.fetchAnyB())
      .map((fetchedB) => this.transformB(fetchedB));
  //            ^ Expected type _any_ but Received _ResultAsync<any>_
  }

  fetchAnyA(): ResultAsync<any> {
    return ResultAsync.from(Promise.reject()).mapError(() => 'Could not load');
  }

  fetchAnyB(): ResultAsync<any> {
    return ResultAsync.from(Promise.reject()).mapError(() => 'Could not load');
  }

  transformB(value: {}) {}
}

UPDATE

If I use .bind instead of .map the code works as expected. 🚀

Expected behavior My expectation is, that ResultAsync is transparent for the Chain, but ResultAsync is passed to the next operator.

Screenshots CleanShot 2022-04-20 at 13 37 41@2x

Desktop (please complete the following information):

Additional context I am willing to help to fix this issue if it becomes confirmed as bug.

seangwright commented 2 years ago

I believe this is the correct behavior.

Just like with Result<T>, .bind will flatten the Result<T> and .map will convert its inner value.

In CSharpFunctionalExtensions, the async Bind returns a Task<Result<T>>, but async Map would return a Task<Result<Result<T>> if K in Func<T, Task<K>> func was a Result<T>.

GregOnNet commented 2 years ago

Cool, thanks for the feedback. I was not ware that bind is the flattening-Operation, here. 👍