Search code examples
typescripterror-handlingtaskeitherfp-ts

Unwrapping a task either, handling errors, and providing default value


Say I have some async function which fetches from an api and returns a taskEither (for simplicity, I'm providing a basic async task either:

const someAsyncFunction = taskEither.fromPredicate(
  (value: number) => value < 12,
  (value: number) => new Error("value was greater than 12")
)

I then want to report the error to sentry if it exists, however, I wish to inject a default value and continue downstream execution

const reportError = (error: Error) => console.log(error.message)

type ExceptionHandler = typeof reportError

const unwrapAndReportError = <R>(captureFunction: ExceptionHandler) => (
  def: R
) => (inputEither: taskEither.TaskEither<Error, R>): R =>
  E.getOrElse((err: Error) => {
    captureFunction(err)
    return def
  })(inputEither)

const runAsyncFunctionThenUnwrapAndReport = flow(
  someAsyncFunction,
  unwrapAndReportError(reportError)("potato")
)

This code fails to typecheck with:

Argument of type 'TaskEither<Error, R>' is not assignable to parameter of type 'Either<Error, R>'

Fully runnable example: https://codesandbox.io/s/fp-ts-playground-forked-dy2xyz?file=/src/index.ts:0-722

My question is, how can this effect be achieved using fp-ts? What is the recommended pattern here (and how can I get it to typecheck).


Solution

  • Looks like you're pretty close, getOrElse should work, although I think you're importing that from the Either module. Replace that with an import from the TaskEither module. Also, it's more idiomatic to use pipe so you can supply your task either argument first.

    Perhaps something like this?

    import * as TE from "fp-ts/lib/TaskEither"
    import * as T from "fp-ts/lib/Task"
    import { pipe } from "fp-ts/lib/function"
    
    const someAsyncFunction = TE.fromPredicate(
      (value: number) => value < 12,
      (value: number) => new Error("value was greater than 12")
    )
    
    const reportError = (error: Error) => console.log(error.message)
    
    type ExceptionHandler = typeof reportError
    
    const unwrapAndReportError = <R>(captureFunction: ExceptionHandler) => (
      def: R
    ) => (inputEither: TE.TaskEither<Error, R>): Promise<R> =>
      pipe(
        inputEither,
        TE.getOrElse(
          (error) => {
            captureFunction(error)
            return T.of(def)
          },
        ),
      )()
    
    const runAsyncFunctionThenUnwrapAndReport = flow(
      someAsyncFunction,
      unwrapAndReportError(reportError)("potato")
    )