Change the Either type signature

This commit is contained in:
Michal Vanko 2022-03-16 15:30:08 +01:00
parent 8abf4923a4
commit 5f84339d77

View File

@ -7,6 +7,7 @@ tags:
- Development - Development
- Guide - Guide
--- ---
We have started a new small internal project for automating a few workflows around counting worked hours and time offs. We have started a new small internal project for automating a few workflows around counting worked hours and time offs.
## Application architecture ## Application architecture
@ -43,11 +44,11 @@ export class InternalError extends Error {
} }
} }
``` ```
Then, we can create multiple app-specific errors by extending from these two. Then, we can create multiple app-specific errors by extending from these two.
> We need to set `Object.setPrototypeOf(...)` as TypeScript [introduced a breaking change](https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work) that may cause the inheritance to now work properly. > We need to set `Object.setPrototypeOf(...)` as TypeScript [introduced a breaking change](https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work) that may cause the inheritance to now work properly.
## Handling errors ## Handling errors
I wanted to have a similar style of handling the error as the [Ramda's `tryCatch`](https://ramdajs.com/docs/#tryCatch) function. I couldn't just use Ramda's `tryCatch` as it doesn't support asynchronous functions. I've found inspiration in the [`fp-ts` `TaskEither` type](https://gcanti.github.io/fp-ts/modules/TaskEither.ts.html). I wanted to have a similar style of handling the error as the [Ramda's `tryCatch`](https://ramdajs.com/docs/#tryCatch) function. I couldn't just use Ramda's `tryCatch` as it doesn't support asynchronous functions. I've found inspiration in the [`fp-ts` `TaskEither` type](https://gcanti.github.io/fp-ts/modules/TaskEither.ts.html).
@ -59,7 +60,7 @@ I've come up with the following solution:
* Tuple of error and a result of an asynchronous task that might throw an error * Tuple of error and a result of an asynchronous task that might throw an error
*/ */
export type Either<ResultType, ErrorType extends Error> = Promise< export type Either<ResultType, ErrorType extends Error> = Promise<
[error: ErrorType | null, result?: ResultType] [error: null, result: ResultType] | [error: ErrorType, result: undefined]
> >
/** /**
@ -102,7 +103,9 @@ As most of the errors will be possibly handled by the same `errorHandle` we can
* It will throw an error if any unexpected error is thrown * It will throw an error if any unexpected error is thrown
* When used with `wrapWithErrorHandler` it is able to catch most of the expected errors * When used with `wrapWithErrorHandler` it is able to catch most of the expected errors
*/ */
export function genericErrorHandler<ErrorType extends Error>(exception: ErrorType | unknown) { export function genericErrorHandler<ErrorType extends Error>(
exception: ErrorType | unknown
) {
if (exception instanceof PublishableError) { if (exception instanceof PublishableError) {
// These errors are usually handled no need to log them // These errors are usually handled no need to log them
return exception return exception
@ -129,9 +132,10 @@ To spare some characters of redundant code I've applied a partial application pr
/** /**
* Helper function with applied `genericErrorHandler` to `wrapWithErrorHandler` * Helper function with applied `genericErrorHandler` to `wrapWithErrorHandler`
*/ */
export function wrapWithGenericErrorHandlerFunction<FunctionArgs extends Array<any>, ResultType>( export function wrapWithGenericErrorHandlerFunction<
fn: (...args: FunctionArgs) => Promise<ResultType> FunctionArgs extends Array<any>,
) { ResultType
>(fn: (...args: FunctionArgs) => Promise<ResultType>) {
return wrapWithErrorHandler(genericErrorHandler, fn) return wrapWithErrorHandler(genericErrorHandler, fn)
} }
``` ```
@ -141,11 +145,13 @@ export function wrapWithGenericErrorHandlerFunction<FunctionArgs extends Array<a
To sum it up, I'd like to present you the way how this API is used: To sum it up, I'd like to present you the way how this API is used:
```typescript ```typescript
const [error, memberId] = await wrapWithGenericErrorHandlerFunction(getUserId)(user.token) const [error, memberId] = await wrapWithGenericErrorHandlerFunction(getUserId)(
if (error) { user.token
)
if (error) {
await respond(error.message) await respond(error.message)
return return
} }
``` ```
This API allows me to keep the separation of concerns between the tasks and error handling into two separate functions. It also allows us to use the `wrapWithErrorHandler` with different `errorHandler` when we need to take special care. Also, I am still able to throw errors from the `errorHandler` that might be caught by a different `errorHandler` from an upper scope. This API allows me to keep the separation of concerns between the tasks and error handling into two separate functions. It also allows us to use the `wrapWithErrorHandler` with different `errorHandler` when we need to take special care. Also, I am still able to throw errors from the `errorHandler` that might be caught by a different `errorHandler` from an upper scope.