The retry-ts library provides tools to define retry policies and execute asynchronous tasks with those policies. You can compose policies using Monoid.concat to combine different strategies like backoff, delay capping, and retry limits.
To use it with fp-ts tasks, use the retrying function from retry-ts/Task. This function takes a policy, a function that receives the current RetryStatus (allowing for side effects like logging based on the current attempt), and a predicate to determine if the operation should be retried (e.g., checking if an Either is a Left).
import { log } from 'fp-ts/Console'
import * as E from 'fp-ts/Either'
import { pipe } from 'fp-ts/function'
import * as O from 'fp-ts/Option'
import * as TE from 'fp-ts/TaskEither'
import { capDelay, exponentialBackoff, limitRetries, Monoid, RetryStatus } from 'retry-ts'
import { retrying } from 'retry-ts/Task'
// Define a policy: exponential backoff starting at 200ms, capped at 2000ms, with a maximum of 5 retries
const policy = capDelay(2000, Monoid.concat(exponentialBackoff(200), limitRetries(5)))
const fakeAPI = TE.left('API errored out')
// A side-effect function that logs the current status
const logDelay = (status: RetryStatus) =>
TE.rightIO(
log(
pipe(
status.previousDelay,
O.map((delay) => `retrying in ${delay} milliseconds...`),
O.getOrElse(() => 'first attempt...')
)
)
)
// Execute the task with the policy
const result = retrying(policy, (status) => pipe(logDelay(status), TE.apSecond(fakeAPI)), E.isLeft)
result().then((e) => console.log(e))