Warning: Effeck is v0. The language will change in major, breaking ways. It is not suitable for production use.

Retrying

Retry transient domain failures when a new attempt has a useful chance of succeeding. A retry runs the supplied program again from its beginning, including its side effects.

Upstream page

Retrying failed computations

error Busy { attempt: number }

function loadWithRetries() {
  let attempts = 0
  return retry({ times: 3 }) {
    attempts += 1
    if (attempts < 3) throw new Busy({ attempt: attempts })
    return "Snapshot ready"
  }
}

The initial attempt always runs. { times: 3 } allows three additional attempts, for at most four executions; this example succeeds on its third execution. State declared outside the retry callback, such as attempts, persists across attempts, while local variables inside it start fresh.

Failures stop the current attempt before retry begins. Defects and interruption are not retry signals. Retried writes need an application policy that makes repeated execution safe.

Selecting retryable domain errors

error RequestRejected {
  transient: boolean
}

function sendRequest(): string!RequestRejected! {
  throw new RequestRejected({ transient: false })
}

function sendTransient() {
  return retry(sendRequest(), {
    times: 4,
    while: (error) => error.transient
  })
}

while permits retry while its predicate holds; until stops retry when its predicate holds. Both can return a boolean or an Effect, so selection can consult a service when needed. Unselected errors remain typed failures rather than becoming successful results.

Controlling timing and limits

function sendWithBackoff() {
  return retry(sendRequest(), {
    times: 4,
    while: (error) => error.transient,
    schedule: Schedule.exponential("150 millis")
  })
}

function sendThreeMoreTimes() {
  return retry(sendRequest(), Schedule.recurs(3))
}

A schedule controls delay and can add its own stopping condition. Combining times, predicates, and a schedule stops when the applicable policy says to stop. Schedule.recurs(3) permits three retries; use Effect.repeat for repetition after success.

Recovering after retries end

function sendOrQueue() {
  return Effect.retryOrElse(
    () => sendRequest(),
    Schedule.recurs(2),
    (error, retries) => {
      log(`Queued after ${retries} retries; transient=${error.transient}`)
      return "Queued for later"
    }
  )
}

retryOrElse runs recovery after its schedule is exhausted. The callback receives the last error and the schedule's output, which is a retry count for Schedule.recurs. The recovery computation can introduce its own errors and requirements.

Coming from Effect

Pass () => sendRequest() to retry. Passing the result of sendRequest() would run the request before the retry policy receives it.