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

toPromise vs "toPromiseUnboxed" #14

Closed GregOnNet closed 1 year ago

GregOnNet commented 1 year ago

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

Who should know about Result or ResultAsync? Often, this question comes to my mind.

I use Result-Monad for orchestrating multiple operations. Once this is finished, I want to pass the outcome to the consumer.

In my web application, often a Controller is the consumer. I think the Consumer would be fine getting a Promise that is either in Success- or Error-State.

Currently, when toPromise is called a Promise<Result<T>>.

Describe the solution you'd like

I would like to have a function like toPromiseUnboxed that unwraps the Result and creates either a resolved or rejected Promise.

Describe alternatives you've considered

Until this moment, I do the result.hasValue()|hasError()-Dance. Which causes writing more code.

Additional context Add any other context or screenshots about the feature request here.

seangwright commented 1 year ago

That makes sense. Promise is already a kind of Result with Promise.resolve and Promise.reject.

So we'd need some alternative to this ResultAsync method that converted the inner Result into a Promise:

  toPromise(
    errorHandler?: FunctionOfTtoK<unknown, Some<TError>>
  ): Promise<Result<TValue, TError>> {
    return isDefined(errorHandler)
      ? this.value.catch((error) => Result.failure(errorHandler(error)))
      : this.value;
  }

I think we'll just need to come up with a name that fits.

GregOnNet commented 1 year ago

For me, it is a real challenge to come up with a name. :-D.

Maybe resolvePromise() ?

seangwright commented 1 year ago

🤔 yeah, it is hard to come up with a name.

seangwright commented 1 year ago

In looking at how Promise is typed in TypeScript, I noticed that we'd lose the 'failure' type information if we converted Result<T, E> to Promise<T> because Promise doesn't have a generic type for the 'reason'/'error', as mentioned by one of the TypeScript maintainers.

I think that while converting a Result<T, E> to Promise<T> is something we can do, losing the type information here would be unfortunate and I'm not sure adding this to the API of ResultAsync leads to good experience.

Do you have a code example of where you have to use result.hasValue(),hasError() and something like this wouldn't work?

function getData(): ResultAsync<number, string> {
    return ResultAsync.success<number, string>(1);
}

class Component {
  num = 0;
  error = '';

  async init() {
    await getData()
       .match({
          success: num => this.num = num,
          failure: err => this.error = err
       });
  }
}

What is the Controller doing in your application?

It's common for Result/Maybe to bubble up to the 'edge' of an application and do the unwrapping there. When I use ResultAsync in my Vue applications, it's the central store that works with them and that store's state is modified based on the state of the ResultAsync.

If you really wanted to pass a rejected Promise to the caller, you could use .match() and throw in the failure case:

import { noop, ResultAsync } from 'typescript-functional-extensions'; // v1.4.0+

const promise = ResultAsync.success<number, string>(1)
   .match({
      success: noop<number>, // or num => num, but noop clearly shows no operation is intended
      failure: err => { throw Error(err); }
   });

await promise
    .then(num => console.log(num))
    .catch(err => console.error(err));
GregOnNet commented 1 year ago

Hi Sean,

match really helps here. I was not aware that match is awaitable.

It also feels better to stay in the monad.

This issue is related to #13 where we discussed various use cases and ways how to approach certain situations in code.

I wonder if our discussion could be distilled to a blog post or documentation showing, when to use what. Goal help to think in Result-Monad + Demonstrating real world examples.

Currently, I am using it a lot with NestJS. You mentioned that you use it in Vue. Maybe we can write Backend and Frontend-Guides.

Cheers Gregor

seangwright commented 1 year ago

I did just overhaul the docs a bit and I've had an issue to add some more scenario based tests or examples.

I really like the idea of a blog post or various front-end/back-end guides with common patterns.

seangwright commented 1 year ago

I'm going to close this in favor of #17 where we can discuss any ideas to take this further.