When to return a Result.Error and when to throw an Exception?
Tags: javascript, typescript, rop, functional
Published: 2019-06-12 18:07:19

---
title: When to return a Result.Error and when to throw an Exception?
published: true
tags: javascript,typescript,rop,functional
canonical_url: https://patrickroza.com/blog/when-to-return-a-result-error-and-when-to-throw-an-exception/
series: Functional Programming - Clean architecture and DDD
---

In the previous part I introduced the Result class as a means of control flow and error handling. Now I will focus on when to use the Result class to return an Error, and when to throw instead.

There appears to be quite some different opinions on the subject:

1. Always throw an Error
2. Always throw errors and then only wrap them when you want to handle them
3. **Only return recoverable errors, throw the rest**
4. **Return expectable errors, throw (or pass through) the rest.**
5. Return every error, never throw. (Wrap any library error)

I am mostly in Camp 4, although I think 3 and 4 actually mean the same thing, most of the time. As always, it depends on the use case.

I would generally return: **Domain** errors and **certain**  **Infrastructure** errors

- _SoldOut_, _RoomAlreadyBooked_, _PaperJammed_, _InvalidProductCode_, _CodeAlreadyRedeemed_, _PaymentMethodFailed_
- _ValidationError_, _InvalidStateError_, _ForbiddenError_, _AuthenticationRequiredError_
- _APIError_/_DBError_: _RecordNotFound_, _RecordAlreadyExists_, _(Optimistic)LockError_, _CouldNotAcquireLockError_, _ConnectionError_, _RemoteServiceError_

I would throw exceptions (panics):

Programmer errors:

- _ArgumentException_
- _NullException_
- _DivideByZeroException_

Corruption errors, e.g: _SerializationException_

- The runtime will also throw various errors, for instance in case of _StackOverflow_ or _OutOfMemory_.Such errors should abandon the current flow, and raise an exception, probably caught at the highest level, usually for error logging.

In the end, it depends on what you're building.

## Catch & Wrap ‘all the tings’?

Does this mean you should catch and wrap **all** third party errors? No, it follows the same principals; if it falls into expectable errors like one that represents api call status codes like 400 or 404, or e.g a database record not found; wrap them.

## Why return instead of throw

> a technique that allows you to capture errors elegantly, without contaminating your code with ugly conditionals and try/ catch statements.

_From the book “Domain modeling made functional”; A great read, really recommended!_

- try/catch/finally is verbose, indenting/nesting at least 1 scope deep, often also leading to multiple levels deep (within 1 or more functions)
- Errors become part of the return type signature; automatic documentation, and compiler assistance when missing to handle one
- It enables composability
- Throwing errors is expensive (stack traces etc), while for the error cases you would return, generally I see no use for stack traces. Now I’ll be the first to say: don’t prematurely optimize, but if you can design your application from the ground up to be performant, even in the expectable error cases; that’s great.

As a side note, in javascript:

- There is no ‘beautiful’ built-in way to handle different error types differently, other than `if` statements or `switch`, unlike e.g in C# or Python.
- There is no ‘beautiful’ built-in way to document the errors that may occur, and have type-safety for them, unlike in e.g Java.

## Other examples of implementations that favor return status over Exceptions

- **fetch** : The fetch implementation will only throw on a Network Error. It will use an `ok` Boolean that will be false on a status code > 400. It isn’t as elegant as the Result class though.
- **Promise** : once resolved or rejected; either success or error. Has the benefit of `await` support however.

## Relationship to "Checked" Exceptions

[https://stackoverflow.com/questions/613954/the-case-against-checked-exceptions](https://stackoverflow.com/questions/613954/the-case-against-checked-exceptions)

[https://softwareengineering.stackexchange.com/questions/150837/maybe-monad-vs-exceptions](https://softwareengineering.stackexchange.com/questions/150837/maybe-monad-vs-exceptions)

Perhaps summated as:

> With great power comes great responsibility

I think the difference is that Checked exceptions are a forced concept , but Result can be an application level decision you make, you opt-in.

In fact, if you don't want to handle a Result.Error, you can just let it bubble up, there's no requirement to handle it, unless you explicitly decide "this is the place I want to handle each and every exception in a certain way", and e.g use Typescript's "exhaustive switch block" or similar.

Finally, in Typescript and F# there are Union types that help you "fold" errors into higher order ones, unlike with Checked exceptions in e.g Java.

## Violation of the Open/Closed Principle?

[https://stackoverflow.com/questions/54882275/do-checked-exceptions-violate-the-open-closed-principle](https://stackoverflow.com/questions/54882275/do-checked-exceptions-violate-the-open-closed-principle)

OCP appears to apply to modules, not methods. Also I think that if you add a new exception, you are more "silently" breaking the contract. At least Return error types don't lie ;-)

## Error handling samples

Handle Success, or Error case

```javascript
doSomething()
  .pipe(match(
    value => ctx.body = value,
    err => ctx.statusCode = 500,
  ))
```

Fallback to a default value

```javascript
const result = doSomething()
  .pipe(orDefault(“some-default-value”))
// if doSomething() fails, use “some-default-value” instead.
```

High level REST api error handler (more elegant ideas welcome!)

```javascript
if (err instanceof RecordNotFound) {
  ctx.body = { message }
  ctx.status = 404
} else if (err instanceof CombinedValidationError) {
  const { errors } = err
    ctx.body = {
    fields: combineErrors(errors),
    message,
  }
  ctx.status = 400
} else if (err instanceof FieldValidationError) {
  ctx.body = {
    fields: { [err.fieldName]: err.error instanceof CombinedValidationError ? combineErrors(err.error.errors) : err.message },
    message,
  }
  ctx.status = 400
} else if (err instanceof ValidationError) {
  ctx.body = { message }
  ctx.status = 400
} else if (err instanceof InvalidStateError) {
  ctx.body = { message }
  ctx.status = 422
} else if (err instanceof ForbiddenError) {
  ctx.body = { message }
  ctx.status = 403
} else if (err instanceof OptimisticLockError) {
  ctx.status = 409
} else if (err instanceof CouldNotAquireDbLockError) {
  ctx.status = 503
} else if (err instanceof ConnectionError) {
  ctx.status = 504
} else {
  // Unknown error
  ctx.status = 500
}
```

Which would be accompanied by a catch-all exception handler for status 500, and logging:

```javascript
try {
  // execute the workflow
  // handle success and error result cases
} catch (err) {
  logger.error(err)
  ctx.status = 500
}
```

## Source

As always you can also find the full framework and sample source at [patroza/fp-app-framework](https://github.com/patroza/fp-app-framework)

## Other reads

- [https://blog.logrocket.com/elegant-error-handling-with-the-javascript-either-monad-76c7ae4924a1/](https://blog.logrocket.com/elegant-error-handling-with-the-javascript-either-monad-76c7ae4924a1/)

## What's Next

Next in the series, I plan to explore my ideal vision of the future, once we receive pipe operator `|>` and improved Generator typing support in Typescript ;-)