DEV Community

Handling errors with Either

Avalander on April 08, 2018

An Either is basically a container for a value that might be an error. With an Either we can apply transformations to the contained value without h...
Collapse
 
napicella profile image
Nicola Apicella

Hi! Nice article. I have been using vavr and its Either type for a while. I still didn't quite figure out how to use it when I need to expose different type of errors in the either. I thought about have different Error type and do a type assertion, but at this point I d better go with exceptions which expose this behaviour naturally(catch clauses with different types). Do you have any thought about it?

Collapse
 
avalander profile image
Avalander • Edited

Well, if you actually need different behaviour for different kinds of errors (as opposed to simply having different data in the error), you need a bit of extra effort to make it work with Either. I don't know about vavr, but it seems to be Java, and Java has a pretty decent exception system, so the easiest might be to just throw and catch checked exceptions.

That being said, Elm has a cool thing called union types which can be used to handle multiple kinds of errors. It's similar to an Either, but it allows more than two branches, each branch with a different behaviour.

To implement that in Javascript I would try something like this:

const UnionType = types => types.reduce((prev, type) => ({
    ...prev,
    [type]: (data) => ({
        match: (fns) => fns[type](data)
    })
}), {})

Then you can create your own union type to handle errors.

const CustomErrors = UnionType([
    'NetworkError',
    'InputError',
    'RandomError',
])

const someError = CustomErrors.RandomError({ message: 'Random' })

someError.match({
    NetworkError: ({ status, message }) => {...},
    InputError: ({ field, value }) => {...},
    RandomError: ({ message }) => console.error(message),
})

You can either have a union type in the left branch of an Either and do the matching when you fold it, or have a union type with a branch for valid values and several branches for invalid values (not sure how that second option would work out, though, you would need to implement map and chain in UnionType also). And, of course, you can use it for a lot of other things, besides handling different kinds of errors.

Now, this is something I just thought that might be interesting to borrow from Elm, but I haven't really tried in Javascript, so use it with caution.

Collapse
 
napicella profile image
Nicola Apicella

Interesting! Gotta read about union types and experiment a bit. Thanks a lot :)

Thread Thread
 
avalander profile image
Avalander

You're welcome, I hope you find it useful! :)

Collapse
 
normancarcamo profile image
Norman Enmanuel

Wow, it's good to see more people in FP these days!, more collaboration is always welcome.
Btw, how would you handle sync/async + composition?
It's a bit hard to handle all these concepts together in JS, in Elixir or Scala I don't see too much problem due to their nature.

Thanks!.

Collapse
 
avalander profile image
Avalander

Thanks for the comment :)

Btw, how would you handle sync/async + composition?

That's a good question. Unfortunately, I don't have a good answer.

Sometimes I just use promises as pseudo-either monads when I have to mix sync and async code and that's good enough.

// Instead of throwing an exception with invalid JSON,
// this function will capture it in a promise's rejection branch.
const parseJson = data => Promise.resolve(data)
    .then(JSON.parse)

// Another sync function wrapped in a promise.
const verifyResponse = ({ statusCode, body }) =>
    statusCode === 200
        ? Promise.resolve(body)
        : Promise.reject({ statusCode, body })

parseJson(someData) // For some weird reason our data is stringified JSON, very convenient to manipulate.
    .then(postData) // We send our data to a remote host
    .then(verifyResponse) // We verify that we get 200 back
    .then(parseJson) // We parse the body of the response
    // ...

Another option would be to map eithers and maybes to promises when composing sync and async functions.

const Left = x => ({
    ...
    toPromise: () => Promise.reject(x),
})

const Right = x => ({
    ...
    toPromise: () => Promise.resolve(x),
})

const map = F => x => F.map(x)
const chain = F => x => F.chain(x)
const toPromise = F => F.toPromise()

const parseJson = data => tryCatch(JSON.parse, data)

const verifyResponse = ({ statusCode, body }) =>
    statusCode === 200
        ? Right(body)
        : Left({ statusCode, body })

parseJson(someData)
    .toPromise()
    .then(postData) // We send our data to a remote host
    .then(verifyResponse) // We verify that we get 200 back
    .then(chain(parseJson)) // In case you want to keep the Either inside the promise
    .then(toPromise) // In case you want to unwrap the Either

Most of the times I think promises as pseudo-eithers is good enough, so I haven't explored how to compose them with proper eithers that much.

Collapse
 
normancarcamo profile image
Norman Enmanuel

Thanks for reply!

I actually used to do what you mention in the first code example, but not in the last example (nice trick to explicitly and descriptively passing from sync to async), at the end of the day when we enter to the async world we cannot exit, even worse, they can be nested, but luckily we have async/await mechanism to flat it.

By the way, I think the operator pipe will fix this.

Collapse
 
christopheriolo profile image
Christophe Riolo

This looks actually very much like monads, do you know about them? :) en.m.wikipedia.org/wiki/Monad_(fun...

Collapse
 
jvanbruegge profile image
Jan van Brügge

Either is a monad. As well as Maybe :)

Collapse
 
christopheriolo profile image
Christophe Riolo

I did not know Either specifically thank you for clearing it :)

Collapse
 
avalander profile image
Avalander

Yep. I didn't use the M word because I didn't want to scare people, but monads are cool. Thanks for bringing it up :)

Collapse
 
christopheriolo profile image
Christophe Riolo

Indeed I did not see the tag, I wasn't sure because of the absence of the usual vocabulary /)