Handling exceptions

throw new Error ('Houston, we have a problem')

const divide = (param1: number, param2: number): number => {
  if (param2 === 0)
    throw new Error("Attempt to divide with zero");

  return param1 / param2;
};

try {
  const result = divide(a, b)
  return `result: ${result}`
} catch (e) { // optional if finally exist
  // Code to run if an exception occurs
} finally { // optional if catch exist
  // Code that is always executed
}

Problems:

- the type of a function says nothing if it can throw an exception.
- difficult to compose the function into a larger computation.

Throwing an exception

Exception-oriented code

Why kill exceptions?

- Introducing alternative futures (side effects).

- Not type-safe 🤖

Special Values

const divide = (
  param1: number,
  param2: number
): number | NaN => {
  if (param2 === 0)
    return NaN;

  return param1 / param2;
};

const result = divide(a, b)
if (Number.isNaN(result)) {
  // console.log(`Result: ${result}`) 
} else {
  // Run code for special case
}

- No specific semantic meaning 🙃
- Creates need for boilerplate error checking code 🤔

- Forces a special policy on callers, makes it difficult to compose with other functions ⚠️

Option data type

const divide = (
  param1: number,
  param2: number
): Option<number> => {
  if (param2 === 0)
    return none;

  return some(param1 / param2)
};

const result = divide(1/2)
if (result instanceof Some) {
  // do something!
  console.log(result.value)
} else if (result instanceof NONE) {
  // Specific error
} else {
  // Another kind of error
}

how does this work? ⬇️

Factoring out common error-handling patterns into higher-order functions

export class None<A>
extends OptionBase<A> {
  // `never` is the "bottom type"
  // `static` creates "class" property
  static readonly NONE:
   Option<never> = new None();

  readonly value: "none" = "none";

  // `private` prevents access
  // by external code
  private constructor() {
    super();
  }
}
export type Option<A> = Some<A> | None<A>;

export const none = <A>(): Option<A> => None.NONE;

export const some = <A>(a: A): Option<A> => new Some(a);
export class Some<A>
extends OptionBase<A> {
  readonly value: "some" = "some";

  // 6. classes must call `super()`
  // if they extend other classes
  constructor(value: A) {
    this.value = value
    super();
  }
}

Example here ▶️

Either data type

const customError = () => ({
  message: `A number cannot be divided by zero`,
})

const divide = (
  param1: number,
  param2: number
): Either<{ message: string }, number> => {
  if (param2 === 0)
    return left(customError())

  return right(param1 / param2)
};

const result = divide(a, b)
if (result.isLeft()) {
  const { message } = result.value;
  // Code to run if an exception occurs
} else {
  return `result: ${result.value}`
}

how does this work? ⬇️

export class Left<L, A> {
  readonly value: L;

  constructor(value: L) {
    this.value = value;
  }

  isLeft(): this is Left<L, A> {
    return true;
  }

  isRight(): this is Right<L, A> {
    return false;
  }
}
export class Right<L, A> {
  readonly value: A;

  constructor(value: A) {
    this.value = value;
  }

  isLeft(): this is Left<L, A> {
    return false;
  }

  isRight(): this is Right<L, A> {
    return true;
  }
}
export type Either<L, A> = Left<L, A> | Right<L, A>;

export const left = <L, A>(l: L): Either<L, A> => {
  return new Left(l);
};

export const right = <L, A>(a: A): Either<L, A> => {
  return new Right<L, A>(a);
};

support of as many errors as we want 💑

Super-charging Either ⚡

export type Either<L, A> = Left<L, A> | Right<L, A>;

export class Left<L, A> {
  // ......
  applyOnRight<B>(_: (a: A) => B): Either<L, B> {
    return this as any;
  }
}

export class Right<L, A> {
  // ......
  applyOnRight<B>(func: (a: A) => B): Either<L, B> {
    return new Right(func(this.value));
  }
}
// ......
const result = divide(1, 2);
return result.applyOnRight(doSomething);

Result ❤️

Common pattern: If there's an error do nothing, else do something...

Creating a chain of operations 🔗

➡️

References

- TypeScript Deep Dive: Exception Handling

- Functional Programming in TypeScript: Handling errors

- Bruno Vegreville: Expressive error handling in TypeScript

- ts-fp: Option data type

Books

- Programming TypeScript: Making Your JavaScript Applications Scale

Handling exceptions

By Juan David Nicholls

Handling exceptions

Handling errors without exceptions in TypeScript, using Option and Either data types of functional programming

  • 356
Loading comments...

More from Juan David Nicholls