Promise and other tools for Monadic error handling
Tags: promise, monad, errors, maybe
Published: 2020-11-17 07:00:37

Today I would like to talk with you about one of the most common utilization for *monads* - *Monadic error handling*. For those, who doesn't have experience with a concept of *monads*, you are welcome to check out my attempt to make a brief introduction to the topic in [a previous part of the series](https://dev.to/airtucha/functors-and-monads-in-plain-typescript-33o1).

# I `Promise` you

Usually, a useful program involves interactions with some remote services, databases or local file storage. There number of reasons for it: 

- A prior state of the application should be loaded during initialization 
- Data changes should be synced during an application execution
- State of an app should be saved on closing phases

This kind of requests can be imagined as a sailing ship sent in a dangerous oversee expedition to transport a valuable resource. Such an adventure might take an arbitrary amount of time. Moreover, it is a risky business. Hence the cargo can be lost in the process. Therefore, the abstraction suitable for modelling operation of this kind should be able to deal with a delay of unknown duration as well as with possible failure. 

This type of work is called asynchronous computation. A special kind of `Monad` commonly represents it. The abstraction with slight variations was implemented under different names in many environments. It is known as `Promise`, `Future`, `Task` and so on. The most widely used one is `JavaScript`'s take on it, which is going to be used as a primary example for the article. `Promise` wraps around the computation. It allows a developer to attach callbacks capable of handling the result of the computation. The function is applied to the attached value as soon as it became available. Following listening presents signature of a JavaScript function responsible for the handling of HTTP/HTTPS requests in an environment of a modern browser.

```typescript
function fetch(
    url: RequestInfo,
    init?: RequestInit
): Promise<Response>;
```

It consumes two arguments describing a request and returns a result inclosed inside of Promise that indicates the asynchronous nature of the operation. Result of the operation can be accessed via callback passed to `Promise.prototype.then()` function.

```typescript
const rawResult = fetch(someURL)
const processedResult = rawResult
    .then( value => {
        // some processing
        const processedValue = processValue(value)
        return value
    })
```

The applications of callbacks can easily be chained. 

```typescript
rawResult
    .then( value => {
        // some processing stage 1
        const processedValue = processValue1(value)
        return value
    })
    .then(value => {
        // some processing stage 2
        const processedValue = processValue2(value)
        return value
    )
```

It is useful in a situation, then the result has to be preprocessed and passed father.

```typescript
function getData() {
    return fetch(someURL)
        .then( value => {
            // some processing stage 1
            const processedValue = processValue1(value)
            return value
        })
}   
const result = getData()
    .then(value => {
        // some processing stage 2
        const processedValue = processValue2(value)
        return value
    )
```

Another remarkably helpful property of `then()` is its ability to chain *functions* which returns `Promise` that occurs in a situation of subsequent requests. For example, there are two *functions*. One of them can get some basic information about the user by his/her name. The information is repressed as an object of `User` type. The second *function* uses part of the information to request pictures of the user.

```typescript
// function capable of getting required data
function getUser( name: string ): Promise<User> {
    return fetch(`${someURL}/${name}`)
        .then(parseUser)
}  
function getUserPic( user: User ) {
    return fetch(`${someURL}/${user.picURL}`)
}
```

The *functions* can be utilized in the following way to retreat a user's image by name.

```typescript
// utilization of the functions
const picPromise = getUser('Alex T')
    .then( user => {
        return getUserPic(user)
    )
picPromise.then( pic => {
    // handle the picture
})
```

As it was previously mentioned *Promise* is also capable of modelling errors. An error is dispatched to a callback passed to `Promise.prototype.catch()` function or as the second argument to `Promise.prototype.then()`. It can handle the problem by returning a fallback value.

```typescript
result
    .catch( err => {
        // error preprocessing 
    })
    .catch( err => {
        // error handling
        return fallBackValue
    })
```

# Monads for error handling

As it was previously shown `Promise` could model asynchronous computations as well as computations that might fail. The coupling is not entirely necessary hence *Rust*'s implementation of similar abstractions is only responsible for the handling of asynchronous operations. On the other hand, it might also be useful to have monadic abstractions purely dedicated to the representation of possibly failing computations or missing data. The differentiation between three of the cases might help to communicate an intention behind code in a more clear way. Getting rid of asynchrony allows having optimized lightweight implementation and extended specialized APIs.

This approach of error handling is an alternative to `exceptions`. Several relatively recent languages chose the approach like *Scala*, *Haskell*, *Rust*, *Elm*, *Clojure* and many others. The main advantage of such a solution is explicitness. Possibility of failure is clearly indicated in the signature of each particular function; hence data can not be directly accessed. Therefore, a developer is forced to deal with it in a way that error handling became an integral part of the code.

So, this approach might be preferable in a situation where code is meant to fail. For example, the application might rely on frequently incorrect input like in case of compilers or other untrusty data sources. Have you ever heard about outstanding error messages produced by compilers of *Rust* and *Elm*?

# Conclusion

`Monads` can be used for modelling of computation with high latency or possibilities of failure. Depending on the implementation, the abstraction can handle both of the situations or focus on just one of them. In the following article, we will take a look at examples of `Monads` specialised on error handling. 

# Disclaimer

`Promise` doesn't strictly follow the mathematical definition of `Monad`. The implementation has some minor limitations, which are neglectable in most of production use cases. Moreover, it works as a perfect example of a mechanic useful for a wide audience.