1

After reading an article about Handling Failure by Vladimir Khorikov, I try to implement it using TypeScript.

The example code was written in C#, which I can understand only the idea, not the code.

Here is my implementation so far.

export class Result<T> {
  success: boolean;
  private error: string | null;
  private _value: T;

  get value(): T {
    if (!this.success) {
      throw new Error('Cannot get value of an error result.');
    }

    return this._value;
  }

  get errorMessage(): string {
    if (this.success || this.error === null) {
      throw new Error('Cannot get error message of a success result.');
    }

    return this.error;
  }

  get failure(): boolean {
    return !this.success;
  }

  private constructor(success: boolean, error: string | null, value?: T) {
    if (success && error != null) {
      throw new Error('Success result cannot have error message');
    }

    if (!success && (error === null || error.length === 0)) {
      throw new Error('Failed result must have error message');
    }

    this.success = success;
    this.error = error;
    this._value = value as T;
  }

  /**
   * Create failure result
   * @param errorMessage error message
   * @returns failureResult
   */
  static fail<T>(errorMessage: string): Result<T> {
    return new Result(false, errorMessage);
  }

  /**
   * Create success result
   * @param value
   * @returns successResult
   */
  static ok<T>(value?: T): Result<T> {
    return new Result(true, null, value);
  }

  /**
   * Combine multiple results into one result
   * @param results
   * @returns result
   */
  static combine(...results: Result<any>[]): Result<any> {
    for (let result of results) {
      if (result.failure) {
        return result;
      }
    }

    return Result.ok();
  }

  /**
   * Do things on success result
   * @param func
   * @returns result
   */
  onSuccess(func: Function): Result<T> {
    if (this.success) {
      func();
    }

    return this;
  }

  /**
   * Do things on failure result
   * @param func
   * @returns result
   */
  onFailure(func: Function): Result<T> {
    if (this.failure) {
      func();
    }

    return this;
  }

  /**
   * Do things on both success and failure result
   * @param func
   * @returns result
   */
  onBoth(func: Function): Result<T> {
    func();

    return this;
  }
}

I am struggling to implement the chain functions.

In the example code, the onSuccess, onFailure, and onBoth functions are chained beautifully.

return Result.Combine(billingInfoResult, customerNameResult)
        .OnSuccess(() => _paymentGateway.ChargeCommission(billingInfoResult.Value))
        .OnSuccess(() => new Customer(customerNameResult.Value))
        .OnSuccess(
            customer => _repository.Save(customer)
                .OnFailure(() => _paymentGateway.RollbackLastTransaction())
        )
        .OnSuccess(() => _emailSender.SendGreetings(customerNameResult.Value))
        .OnBoth(result => Log(result))
        .OnBoth(result => CreateResponseMessage(result));

But in my code, it can only trigger void function. The function can't pass its result to the next function.

Could you show me how to implement this? Any suggestions would be appreciated.

6
  • JavaScript has .next() for promises. Are you sure you need the Result object to do the same thing? Commented Nov 26, 2021 at 3:40
  • The functions must return an instance of Result<T> in his example, if I'm not mistaken. Commented Nov 26, 2021 at 3:41
  • Maybe look at Expressive error handling in TypeScript.... Commented Nov 26, 2021 at 3:47
  • @Invizi Where is next() defined on a Promise? I've seen it on iterators, but Promises have then(), catch(), finally() on instances... Commented Nov 26, 2021 at 3:48
  • @HereticMonkey I meant then, been using another language where it's next instead. Commented Nov 26, 2021 at 3:50

2 Answers 2

1

You seem to be implementing it differently?

Original Code shows:

public static Result OnSuccess(this Result result, Func<Result> func)
{
    if (result.Failure)
        return result;

    return func();
}

which would translate to:

onSuccess(fn: () => Result): Result {
    if (this.failure)
        return this;

    return fn();
}

but you have

onSuccess(func: Function): Result<T> {
    if (this.success) {
      func();
    }

    return this;
}
Sign up to request clarification or add additional context in comments.

3 Comments

So, the OP is missing the return before func();? That's really the only difference I see, besides a more specific typing of the function.
Also the failure check instead of success. Thought it might be helpful to point out in case it was done by mistake.
This only return Result type, but in the example code, the OnSuccess could return Customer type, which is not Result type at all. If we let the function return any, then we could not chain the functions anymore.
0

Following the idea of the article that was suggested by Invizi, I have come up with this solution.

 onSuccess<U>(fn: (value: T) => U): Result<U> {
    if (this.success) {
      return Result.ok(fn(this._value))
    }

    return Result.fail(this.errorMessage)
  }

This makes the chain flexible with type just like the example code.

Result.combine(Rectangle.from(5, 5), Rectangle.from(1, 1))
  .onSuccess(() => {return new User('user')}) // Create new user
  .onSuccess(user => console.log(user.name)) // Print user name

3 Comments

This is not an onSuccess function, this is known as fmap (or just map).
@Bergi It basically changes from one type to another type, so it may look like map. map is used in array, but this is used in object. I get this idea from the article I have mentioned above, so I don't know if he got this idea from map or not :)
It does look like map because that is the method that all functors share. Yes, array is also a functor, just like Result or the equivalent Either type from the other article (the latter term is more common in functional programming).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.