# A declarative approach to error handling in Typescript

Sooner or later, your application will encounter some errors. In a perfect world, we would like to catch this error and respond accordingly.

Sadly, as our codebase grows, it gets more difficult to manage errors in our applications efficiently.

It's like that because in Typescript errors are treated as exceptions - something that should never happen, and should be dealt with right away.

Let's consider the following code using the regular, imperative approach to error handling:

```typescript
const parseJson = (json: string): any => {
  try {
    return JSON.parse(json);
  } catch (error) {
    // what do I do here?
  }
};

const user = parseJson('{"name": "John"}');
const user2 = parseJson('boom');
```

There are several problems with this code.

In the `parseJson` function, we are not able to properly react in case parsing fails. Should we return `null`, `undefined`, or maybe the `error` object?

Moreover, how should we type this function? Should it return `any` or a specific union type?

All of those options will only complicate things for us, as later in the code, every time we'd like to do something with this value we'd have to check if it's `null`, `undefined` or `Error`.

If we wanted to make sure that the parsed user will not break our app in a different place, we'd have to use more `if` statements and `try-catch` blocks in our code.

```typescript
const user = parseJson('{"name": "John"}');
const user2 = parseJson('boom 💥');

const getUserName = (user) => user.name;

getUserName(user); // Ok
getUserName(user2); // Again, boom 💥
```

That's why the imperative approach is not a good way to handle errors. Let's see how this could be written in a declarative way.

## Imperative vs Declarative programming

Let's start by explaining what is imperative and declarative programming.

Wikipedia defines those terms as:

> Imperative programming focuses on describing how a program operates step by step, rather than on high-level descriptions of its expected results. The term is often used in contrast to declarative programming, which focuses on what the program should accomplish without specifying all the details of how the program should achieve the result.

So, in imperative programming, we tell the program step by step what it needs to do. On the other hand, in declarative programming, we operate on the logic, without dwelling too much on the implementation details. But then, how does the program know how to handle a specific case?

We can create a layer of abstraction to contain our "imperative" logic.

## Result type

Earlier in this post, I said that javascript treats Errors as exceptions. What if we treated them as a normal value?

Meet the `Result<Ok, Err>` type. It's a data structure that can contain a correct value or an error.

How could we use it to fix the issues from our example?

The Result comes with two constructors: `Ok()` and `Err()`. We can use them to create, a correct value, and an errored value. Like this:

```typescript
const parseJson = (json: string): Result<User, Error> => {
  try {
    const val = JSON.parse(json);

    return Ok(val)
  } catch (error) {
    return Err(error)
  }
};

const user: Result<User, Error> = parseJson('{"name": "John"}');
const user2: Result<User, Error> = parseJson('boom 💥');
```

Now `parseJson` function always returns a `Result<User, Error>` type.

Seeing that it's a `Result` will tell us right away that it comes from an operation that may have failed. Of course, at this stage, we will have no idea if the Result is failed or not.

But that's the point. We don't need to know that. We can specify operations that will be run ***only*** if the value is valid. 

See... The `Result` is a magic box called a `monad`. Don't let that term scare you. It's much simpler than it seems.


## How to implement a Result monad

Let's start by creating an interface for our type:

```typescript
export default interface Result<Ok, Err> {
  map<O>(f: (value: Ok) => O): Result<O, Err>;
  flatMap<O>(f: (value: Ok) => Result<O, Err>): Result<O, Err>;
  match<O>(x: { Ok: (v: Ok) => O; Err: (v: Err) => O }): O;
}
```

Firstly, we can see two very similar methods: `map` and `flatMap`. They both allow us to apply transformations to the value inside the monad.

`match` is a method that enables us to perform pattern matching on the monad. We can specify two functions, one for the `Ok` value, and another for the `Err` value. That way, we will be able to safely unwrap the monad at any time, and act accordingly to the underlying value.

### Ok and Err constructors

Let's start by implementing a happy path - when nothing goes wrong.

```typescript
export const Ok = <Ok, Err = any>(value: Ok): Result<Ok, Err> => ({
   map: <O>(f: (value: Ok) => O): Result<O, Err> => {
    try {
      return Ok<O, Err>(f(value));
    } catch (e) {
      return Err<O, Err>(e as Err);
    }
  },
  flatMap: <O>(f: (value: Ok) => Result<O, Err>): Result<O, Err> => {
    try {
      return f(value);
    } catch (e) {
      return Err<O, Err>(e as Err);
    }
  },
  match: ({ Ok }) => Ok(value)
});
```

The implementation for an `Err` constructor looks like this:

```typescript
export const Err = <Ok, Err = any>(value: Err): Result<Ok, Err> => ({
  map: <O>(_: (value: Ok) => O): Result<O, Err> => Err(value),
  flatMap: <O>(_f: (value: Ok) => Result<O, Err>): Result<O, Err> => Err(value),
  match: ({ Err }) => Err(value)
});
```

The implementation of the `Ok` constructor looks normal: we are evaluating the functions provided as a callback. If they throw any errors, they're caught and `Err` is returned.

But there's something off with the `Err` constructor.

In `map` and `flatMap` methods, we're not calling the function provided as a callback. The new `Err` instance is returned right away!

That's because we don't want to run any more operations on a failed value. Once Result is failed, it stays failed forever.

That allows us to run the following code without needing to try-catch any new exceptions:

```typescript
parseJson('{"name": "John"}').map(x => x.name) // Result<string, Error>
parseJson('boom 💥').map(x => x.name) // Result<string, Error>
```

As long as the values are wrapped in the `Result` monad, we can apply as many operations as we want. If any of them throws an error, all next operations will be aborted.

### Unwrapping the Result

Ok, that's fine, but we don't want to store the values and errors in the Result forever. Eventually, the time comes to unwrap them and handle both cases.

This can be done with the `match` method we've implemented.

Let's create a new function that will consume the Result and will return a string:

```typescript
const greetUser = (user: Result<User, Error>): string =>
  user
    .map((user) => user.name)
    .match({
      Ok: (name) => `Hello, ${name}`,
      Err: (error) => {
        console.error(error); // log the error
        return 'Hello, Stranger'; // gracefully handle the case
      },
    });
```

It can be used like this:

```typescript
const user = parseJson('{"name": "John"}');
const user2 = parseJson('boom 💥');

greetUser(user) // "Hello, John"
greetUser(user2) // "Hello, Stranger"
```

### Wrap early, unwrap late

It's a powerful concept. Now we can store our `Result` in the state and pass it around to other functions, without needing to worry if it will crash our app. 

The only thing is to wrap the value in the `Result` as soon as it may fail and unwrap it only when we need to use it directly.

Consider the following example using React:


```jsx
const OrderDetails: React.FC<{ order: Result<Order, OrderError> }> = ({
  order,
}) =>
  order
    .map((order) => order.details)
    .match({
      Ok: (details) => (
        <div>
          <h2>{details.name}</h2>
          <ul>
            <li>{details.address}</li>
            <li>{details.city}</li>
            <li>{details.state}</li>
          </ul>
        </div>
      ),
      Err: (error) => (
        <div>
          <h2>Something went wrong</h2>
          <div>{error.message}</div>
        </div>
      ),
    });
```

We didn't need any `if` statements or `try-catch` blocks in this component. That's because the `order` prop is a `Result` type. 

It may have been stored in a state, fetched from an API it created from other data - doesn't matter. All that time it was wrapped in a Result. Now, when the time comes to display it on the screen, we can nicely handle the error where it needs to be handled.

What is more, we can persist the error message and print it on the screen without passing more props to the component.

### Conclusion

`Result` monad is a powerful tool, helping us to handle errors in a more declarative way.

There are other useful monads, that can boost our declarative programming skills - if you haven't read my `Maybe` monad post, I highly encourage you to do so!
