Functional async patterns using TaskEither for type-safe error handling in TypeScript
TaskEither combines the laziness of Task with the error handling of Either, providing a powerful abstraction for async operations that can fail.
import * as TE from 'fp-ts/TaskEither'
import * as T from 'fp-ts/Task'
import * as E from 'fp-ts/Either'
import { pipe, flow } from 'fp-ts/function'
TaskEither<E, A> is equivalent to () => Promise<Either<E, A>>
E = Error type (left)A = Success type (right)The primary way to lift Promises into TaskEither:
import * as TE from 'fp-ts/TaskEither'
// Basic tryCatch pattern
const fetchUser = (id: string): TE.TaskEither<Error, User> =>
TE.tryCatch(
() => fetch(`/api/users/${id}`).then(res => res.json()),
(reason) => new Error(String(reason))
)
// With typed errors
interface ApiError {
code: string
message: string
status: number
}
const fetchUserTyped = (id: string): TE.TaskEither<ApiError, User> =>
TE.tryCatch(
() => fetch(`/api/users/${id}`).then(res => res.json()),
(reason): ApiError => ({
code: 'FETCH_ERROR',
message: reason instanceof Error ? reason.message : 'Unknown error',
status: 500
})
)
// Lift an Either into TaskEither
const fromEither: TE.TaskEither<Error, number> = TE.fromEither(E.right(42))
// From a nullable value
const fromNullable = TE.fromNullable(new Error('Value was null'))
const result = fromNullable(maybeValue) // TaskEither<Error, NonNullable<T>>
// From an Option
import * as O from 'fp-ts/Option'
const fromOption = TE.fromOption(() => new Error('None value'))
const optionResult = fromOption(O.some(42)) // TaskEither<Error, number>
// Success value
const success = TE.right<Error, n...